Implement bottom selection menu in chat.

This commit is contained in:
Rashad Sookram 2022-01-11 09:36:21 -05:00 committed by Cody Henthorne
parent 917744f091
commit 3943e670b2
15 changed files with 209 additions and 122 deletions

View file

@ -96,6 +96,7 @@ public class InputPanel extends LinearLayout
private boolean hideForGroupState;
private boolean hideForBlockedState;
private boolean hideForSearch;
private boolean hideForSelection;
private ConversationStickerSuggestionAdapter stickerSuggestionAdapter;
@ -336,6 +337,11 @@ public class InputPanel extends LinearLayout
updateVisibility();
}
public void setHideForSelection(boolean hideForSelection) {
this.hideForSelection = hideForSelection;
updateVisibility();
}
@Override
public void onRecordPermissionRequired() {
if (listener != null) listener.onRecorderPermissionRequired();
@ -515,7 +521,7 @@ public class InputPanel extends LinearLayout
}
private void updateVisibility() {
if (hideForGroupState || hideForBlockedState || hideForSearch) {
if (hideForGroupState || hideForBlockedState || hideForSearch || hideForSelection) {
setVisibility(GONE);
} else {
setVisibility(VISIBLE);

View file

@ -3775,6 +3775,11 @@ public class ConversationActivity extends PassphraseRequiredActivity
searchViewItem.collapseActionView();
}
@Override
public void onBottomActionBarVisibilityChanged(int visibility) {
inputPanel.setHideForSelection(visibility == View.VISIBLE);
}
@Override
public void onForwardClicked() {
inputPanel.clearQuote();

View file

@ -25,6 +25,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Rect;
import android.net.Uri;
import android.os.AsyncTask;
@ -33,7 +34,6 @@ import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@ -55,6 +55,7 @@ import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.text.HtmlCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewKt;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
@ -65,17 +66,19 @@ import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
import org.signal.core.util.DimensionUnit;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
import org.thoughtcrime.securesms.components.ConversationScrollToView;
import org.thoughtcrime.securesms.components.ConversationTypingView;
import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.components.TypingStatusRepository;
import org.thoughtcrime.securesms.components.menu.ActionItem;
import org.thoughtcrime.securesms.components.menu.SignalBottomActionBar;
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner;
@ -160,20 +163,23 @@ import org.thoughtcrime.securesms.util.TopToastPopup;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.WindowUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.views.AdaptiveActionsToolbar;
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import kotlin.Unit;
@ -224,6 +230,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
private LayoutTransition layoutTransition;
private TransitionListener transitionListener;
private View reactionsShade;
private SignalBottomActionBar bottomActionBar;
private GiphyMp4ProjectionRecycler giphyMp4ProjectionRecycler;
private Colorizer colorizer;
@ -265,6 +272,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
scrollDateHeader = view.findViewById(R.id.scroll_date_header);
toolbarShadow = requireActivity().findViewById(R.id.conversation_toolbar_shadow);
reactionsShade = view.findViewById(R.id.reactions_shade);
bottomActionBar = view.findViewById(R.id.conversation_bottom_action_bar);
final LinearLayoutManager layoutManager = new SmoothScrollingLinearLayoutManager(getActivity(), true);
final ConversationItemAnimator conversationItemAnimator = new ConversationItemAnimator(
@ -739,7 +747,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
});
}
private void setCorrectActionModeMenuVisibility(@NonNull Menu menu) {
private void setCorrectActionModeMenuVisibility() {
Set<MultiselectPart> selectedParts = getListAdapter().getSelectedItems();
if (actionMode != null && selectedParts.size() == 0) {
@ -747,17 +755,86 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
return;
}
setBottomActionBarVisibility(true);
MenuState menuState = MenuState.getMenuState(recipient.get(), selectedParts, messageRequestViewModel.shouldShowMessageRequest(), groupViewModel.isNonAdminInAnnouncementGroup());
menu.findItem(R.id.menu_context_forward).setVisible(menuState.shouldShowForwardAction());
menu.findItem(R.id.menu_context_reply).setVisible(menuState.shouldShowReplyAction());
menu.findItem(R.id.menu_context_details).setVisible(menuState.shouldShowDetailsAction());
menu.findItem(R.id.menu_context_save_attachment).setVisible(menuState.shouldShowSaveAttachmentAction());
menu.findItem(R.id.menu_context_resend).setVisible(menuState.shouldShowResendAction());
menu.findItem(R.id.menu_context_copy).setVisible(menuState.shouldShowCopyAction());
menu.findItem(R.id.menu_context_delete_message).setVisible(menuState.shouldShowDeleteAction());
List<ActionItem> items = new ArrayList<>();
AdaptiveActionsToolbar.adjustMenuActions(menu, 10, requireActivity().getWindow().getDecorView().getMeasuredWidth());
if (menuState.shouldShowReplyAction()) {
items.add(new ActionItem(R.drawable.ic_reply_24_tinted, getResources().getString(R.string.conversation_context__menu_reply_to_message), () -> {
maybeShowSwipeToReplyTooltip();
handleReplyMessage(getSelectedConversationMessage());
actionMode.finish();
}));
}
if (menuState.shouldShowForwardAction()) {
items.add(new ActionItem(R.drawable.ic_forward_24_tinted, getResources().getString(R.string.conversation_context__menu_forward_message), () -> handleForwardMessageParts(selectedParts)));
}
if (menuState.shouldShowSaveAttachmentAction()) {
items.add(new ActionItem(R.drawable.ic_save_24, getResources().getString(R.string.conversation_context_image__save_attachment), () -> {
handleSaveAttachment((MediaMmsMessageRecord) getSelectedConversationMessage().getMessageRecord());
actionMode.finish();
}));
}
if (menuState.shouldShowCopyAction()) {
items.add(new ActionItem(R.drawable.ic_copy_24_tinted, getResources().getString(R.string.conversation_context__menu_copy_text), () -> {
handleCopyMessage(selectedParts);
actionMode.finish();
}));
}
if (menuState.shouldShowDetailsAction()) {
items.add(new ActionItem(R.drawable.ic_info_tinted_24, getResources().getString(R.string.conversation_context__menu_message_details), () -> {
handleDisplayDetails(getSelectedConversationMessage());
actionMode.finish();
}));
}
if (menuState.shouldShowDeleteAction()) {
items.add(new ActionItem(R.drawable.ic_delete_tinted_24, getResources().getString(R.string.conversation_context__menu_delete_message), () -> {
handleDeleteMessages(selectedParts);
actionMode.finish();
}));
}
bottomActionBar.setItems(items);
}
private void setBottomActionBarVisibility(boolean isVisible) {
boolean isCurrentlyVisible = bottomActionBar.getVisibility() == View.VISIBLE;
if (isVisible == isCurrentlyVisible) {
return;
}
int scrollOffset = (int) DimensionUnit.DP.toPixels(34);
if (isVisible) {
ViewUtil.animateIn(bottomActionBar, bottomActionBar.getEnterAnimation());
listener.onBottomActionBarVisibilityChanged(View.VISIBLE);
list.setPadding(list.getPaddingLeft(), list.getPaddingTop(), list.getPaddingRight(), (int) DimensionUnit.DP.toPixels(88));
list.scrollBy(0, -scrollOffset);
} else {
ViewUtil.animateOut(bottomActionBar, bottomActionBar.getExitAnimation())
.addListener(new ListenableFuture.Listener<Boolean>() {
@Override public void onSuccess(Boolean result) {
listener.onBottomActionBarVisibilityChanged(View.GONE);
list.setPadding(list.getPaddingLeft(), list.getPaddingTop(), list.getPaddingRight(), getResources().getDimensionPixelSize(R.dimen.conversation_bottom_padding));
ViewKt.doOnPreDraw(list, view -> {
list.scrollBy(0, scrollOffset);
return Unit.INSTANCE;
});
}
@Override public void onFailure(ExecutionException e) {
}
});
}
}
private @Nullable ConversationAdapter getListAdapter() {
@ -769,9 +846,12 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
}
private ConversationMessage getSelectedConversationMessage() {
Set<MultiselectPart> messageRecords = getListAdapter().getSelectedItems();
Set<ConversationMessage> messageRecords = Stream.of(getListAdapter().getSelectedItems())
.map(MultiselectPart::getConversationMessage)
.distinct()
.collect(Collectors.toSet());
if (messageRecords.size() == 1) return messageRecords.stream().findFirst().get().getConversationMessage();
if (messageRecords.size() == 1) return messageRecords.stream().findFirst().get();
else throw new AssertionError();
}
@ -1130,11 +1210,9 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
if (!TextSecurePreferences.hasSeenSwipeToReplyTooltip(requireContext())) {
int text = ViewUtil.isLtr(requireContext()) ? R.string.ConversationFragment_you_can_swipe_to_the_right_reply
: R.string.ConversationFragment_you_can_swipe_to_the_left_reply;
TooltipPopup.forTarget(requireActivity().findViewById(R.id.menu_context_reply))
.setText(text)
.setTextColor(getResources().getColor(R.color.core_white))
.setBackgroundTint(getResources().getColor(R.color.core_ultramarine))
.show(TooltipPopup.POSITION_BELOW);
Snackbar.make(list, text, Snackbar.LENGTH_LONG)
.setTextColor(Color.WHITE)
.show();
TextSecurePreferences.setHasSeenSwipeToReplyTooltip(requireContext(), true);
}
@ -1204,15 +1282,17 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
private @NonNull String calculateSelectedItemCount() {
ConversationAdapter adapter = getListAdapter();
if (adapter == null || adapter.getSelectedItems().isEmpty()) {
return String.valueOf(0);
int count = 0;
if (adapter != null && !adapter.getSelectedItems().isEmpty()) {
count = (int) adapter.getSelectedItems()
.stream()
.map(MultiselectPart::getConversationMessage)
.distinct()
.count();
}
return String.valueOf(adapter.getSelectedItems()
.stream()
.map(MultiselectPart::getConversationMessage)
.distinct()
.count());
return requireContext().getResources().getQuantityString(R.plurals.conversation_context__s_selected, count, count);
}
@Override
@ -1227,6 +1307,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
void setThreadId(long threadId);
void handleReplyMessage(ConversationMessage conversationMessage);
void onMessageActionToolbarOpened();
void onBottomActionBarVisibilityChanged(int visibility);
void onForwardClicked();
void onMessageRequest(@NonNull MessageRequestViewModel viewModel);
void handleReaction(@NonNull ConversationMessage conversationMessage,
@ -1322,7 +1403,7 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
if (getListAdapter().getSelectedItems().size() == 0) {
actionMode.finish();
} else {
setCorrectActionModeMenuVisibility(actionMode.getMenu());
setCorrectActionModeMenuVisibility();
actionMode.setTitle(calculateSelectedItemCount());
}
}
@ -1806,12 +1887,9 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.conversation_context, menu);
mode.setTitle(calculateSelectedItemCount());
setCorrectActionModeMenuVisibility(menu);
setCorrectActionModeMenuVisibility();
listener.onMessageActionToolbarOpened();
return true;
}
@ -1825,44 +1903,12 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
public void onDestroyActionMode(ActionMode mode) {
((ConversationAdapter)list.getAdapter()).clearSelection();
list.invalidateItemDecorations();
setBottomActionBarVisibility(false);
actionMode = null;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (actionMode == null) return false;
switch(item.getItemId()) {
case R.id.menu_context_copy:
handleCopyMessage(getListAdapter().getSelectedItems());
actionMode.finish();
return true;
case R.id.menu_context_delete_message:
handleDeleteMessages(getListAdapter().getSelectedItems());
actionMode.finish();
return true;
case R.id.menu_context_details:
handleDisplayDetails(getSelectedConversationMessage());
actionMode.finish();
return true;
case R.id.menu_context_forward:
handleForwardMessageParts(getListAdapter().getSelectedItems());
return true;
case R.id.menu_context_resend:
handleResendMessage(getSelectedConversationMessage().getMessageRecord());
actionMode.finish();
return true;
case R.id.menu_context_save_attachment:
handleSaveAttachment((MediaMmsMessageRecord) getSelectedConversationMessage().getMessageRecord());
actionMode.finish();
return true;
case R.id.menu_context_reply:
maybeShowSwipeToReplyTooltip();
handleReplyMessage(getSelectedConversationMessage());
actionMode.finish();
return true;
}
return false;
}
}

View file

@ -108,8 +108,10 @@ class MultiselectItemDecoration(
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val currentSelection = getCurrentSelection(parent)
if (selectedParts.isEmpty() && currentSelection.isNotEmpty()) {
val wasRunning = enterExitAnimation?.isRunning ?: false
enterExitAnimation?.end()
enterExitAnimation = ValueAnimator.ofFloat(enterExitAnimation?.animatedFraction ?: 0f, 1f).apply {
val startValue = if (wasRunning) enterExitAnimation?.animatedFraction else 0f
enterExitAnimation = ValueAnimator.ofFloat(startValue ?: 0f, 1f).apply {
duration = 150L
start()
}
@ -142,7 +144,10 @@ class MultiselectItemDecoration(
if (adapter.selectedItems.isEmpty()) {
drawFocusShadeUnderIfNecessary(canvas, parent)
return
if (enterExitAnimation == null || !isInitialAnimation()) {
return
}
}
shadePaint.color = when {
@ -189,7 +194,9 @@ class MultiselectItemDecoration(
canvas.restore()
}
drawChecks(parent, canvas, adapter)
if (adapter.selectedItems.isNotEmpty()) {
drawChecks(parent, canvas, adapter)
}
}
/**
@ -312,7 +319,8 @@ class MultiselectItemDecoration(
val adapter = parent.adapter as ConversationAdapter
val isLtr = ViewUtil.isLtr(child)
if (adapter.selectedItems.isNotEmpty() && child is Multiselectable) {
val isAnimatingSelection = enterExitAnimation != null && isInitialAnimation()
if ((isAnimatingSelection || adapter.selectedItems.isNotEmpty()) && child is Multiselectable) {
val target = child.getHorizontalTranslationTarget()
if (target != null) {
@ -323,7 +331,7 @@ class MultiselectItemDecoration(
}
val translation: Float = if (isInitialAnimation()) {
max(0, gutter - start) * (enterExitAnimation?.animatedFraction ?: 1f)
max(0, gutter - start) * (enterExitAnimation?.animatedValue as Float? ?: 1f)
} else {
max(0, gutter - start).toFloat()
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/signal_icon_tint_primary" android:pathData="M9.5,5.621v3.7l1.309,0.169a10.932,10.932 0,0 1,9.364 7.253c0.161,0.406 0.8,1.756 0.8,1.756A19.408,19.408 0,0 0,19.5 17.2,17.455 17.455,0 0,0 11.115,14.5L9.5,14.38v4L3.121,12 9.5,5.621m1.137,-3.037a0.758,0.758 0,0 0,-0.491 0.27L1.354,11.646a0.5,0.5 0,0 0,0 0.708l8.792,8.792a0.758,0.758 0,0 0,0.491 0.27c0.219,0 0.363,-0.217 0.363,-0.623V16a14.706,14.706 0,0 1,10.905 5.426c0.282,0.355 0.514,0.529 0.677,0.529 0.214,0 0.3,-0.3 0.222,-0.9C21.822,14.051 18.264,8.934 11,8V3.207c0,-0.406 -0.144,-0.623 -0.363,-0.623Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/signal_icon_tint_primary"
android:pathData="M12.91,3.91h0m1.71,2.16V9.32l-1.31,0.17A11,11 0,0 0,3.84 17c-0.12,0.33 -0.8,1.59 -0.8,1.59s1,-1 1.3,-1.22A17.36,17.36 0,0 1,13 14.5l1.62,-0.12v3.48l-0.06,0.81L15,18 21,12 14.9,5.9l-0.34,-0.54ZM13.48,2.58a0.76,0.76 0,0 1,0.49 0.27l8.79,8.8a0.48,0.48 0,0 1,0 0.7L14,21.15a0.76,0.76 0,0 1,-0.49 0.27c-0.22,0 -0.36,-0.22 -0.36,-0.63V16c-5,0.39 -8.83,2.48 -11.37,6 -0.14,0.2 -0.27,0.29 -0.37,0.29s-0.2,-0.17 -0.16,-0.5C2,14.43 5.59,9 13.12,8V3.21c0,-0.41 0.14,-0.63 0.36,-0.63Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M16.38,4.5C16.1558,3.5058 15.6,2.6174 14.804,1.981C14.008,1.3445 13.0192,0.9978 12,0.9978C10.9808,0.9978 9.992,1.3445 9.196,1.981C8.4,2.6174 7.8442,3.5058 7.62,4.5H2V6H3.5L4.86,20C4.9216,20.5507 5.1842,21.0593 5.5975,21.4284C6.0109,21.7974 6.5459,22.001 7.1,22H16.9C17.4541,22.001 17.9891,21.7974 18.4025,21.4284C18.8158,21.0593 19.0784,20.5507 19.14,20L20.5,6H22V4.5H16.38ZM12,2.5C12.6189,2.5017 13.2222,2.6949 13.7271,3.0529C14.2319,3.411 14.6137,3.9165 14.82,4.5H9.18C9.3863,3.9165 9.7681,3.411 10.2729,3.0529C10.7778,2.6949 11.3811,2.5017 12,2.5V2.5ZM8,18L7.5,8H9L9.5,18H8ZM12.75,18H11.25V8H12.75V18ZM16,18H14.5L15,8H16.5L16,18Z"
android:fillColor="@color/signal_icon_tint_primary"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M22,4.5H16.35C16.1356,3.5056 15.5869,2.6147 14.7954,1.9756C14.0038,1.3366 13.0173,0.9881 12,0.9881C10.9827,0.9881 9.9962,1.3366 9.2046,1.9756C8.4131,2.6147 7.8644,3.5056 7.65,4.5H2V6H3.5L4.86,20C4.9216,20.5507 5.1842,21.0593 5.5975,21.4284C6.0109,21.7974 6.5459,22.001 7.1,22H16.9C17.4541,22.001 17.9891,21.7974 18.4025,21.4284C18.8158,21.0593 19.0784,20.5507 19.14,20L20.5,6H22V4.5ZM12,2.5C12.6189,2.5017 13.2222,2.6949 13.7271,3.0529C14.2319,3.411 14.6137,3.9165 14.82,4.5H9.18C9.3863,3.9165 9.7681,3.411 10.2729,3.0529C10.7778,2.6949 11.3811,2.5017 12,2.5V2.5ZM17.65,19.83C17.6281,20.0139 17.5398,20.1834 17.4017,20.3068C17.2636,20.4302 17.0852,20.4989 16.9,20.5H7.1C6.9148,20.4989 6.7364,20.4302 6.5983,20.3068C6.4602,20.1834 6.3719,20.0139 6.35,19.83L5,6H19L17.65,19.83ZM11.25,18V8H12.75V18H11.25ZM14.5,18L15,8H16.5L16,18H14.5ZM8,18L7.5,8H9L9.5,18H8Z"
android:fillColor="@color/signal_icon_tint_primary"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:autoMirrored="true"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/signal_icon_tint_primary"
android:pathData="M12.91,3.91h0m1.71,2.16V9.32l-1.31,0.17A11,11 0,0 0,3.84 17c-0.12,0.33 -0.8,1.59 -0.8,1.59s1,-1 1.3,-1.22A17.36,17.36 0,0 1,13 14.5l1.62,-0.12v3.48l-0.06,0.81L15,18 21,12 14.9,5.9l-0.34,-0.54ZM13.48,2.58a0.76,0.76 0,0 1,0.49 0.27l8.79,8.8a0.48,0.48 0,0 1,0 0.7L14,21.15a0.76,0.76 0,0 1,-0.49 0.27c-0.22,0 -0.36,-0.22 -0.36,-0.63V16c-5,0.39 -8.83,2.48 -11.37,6 -0.14,0.2 -0.27,0.29 -0.37,0.29s-0.2,-0.17 -0.16,-0.5C2,14.43 5.59,9 13.12,8V3.21c0,-0.41 0.14,-0.63 0.36,-0.63Z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/signal_icon_tint_primary" android:pathData="M9.5,5.621v3.7l1.309,0.169a10.932,10.932 0,0 1,9.364 7.253c0.161,0.406 0.8,1.756 0.8,1.756A19.408,19.408 0,0 0,19.5 17.2,17.455 17.455,0 0,0 11.115,14.5L9.5,14.38v4L3.121,12 9.5,5.621m1.137,-3.037a0.758,0.758 0,0 0,-0.491 0.27L1.354,11.646a0.5,0.5 0,0 0,0 0.708l8.792,8.792a0.758,0.758 0,0 0,0.491 0.27c0.219,0 0.363,-0.217 0.363,-0.623V16a14.706,14.706 0,0 1,10.905 5.426c0.282,0.355 0.514,0.529 0.677,0.529 0.214,0 0.3,-0.3 0.222,-0.9C21.822,14.051 18.264,8.934 11,8V3.207c0,-0.406 -0.144,-0.623 -0.363,-0.623Z"/>
</vector>

View file

@ -49,7 +49,7 @@
android:layout_height="0dp"
android:layout_weight="1">
<FrameLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/fragment_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View file

@ -22,7 +22,7 @@
android:cacheColorHint="@color/signal_background_primary"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingBottom="2dp"
android:paddingBottom="@dimen/conversation_bottom_padding"
android:scrollbars="vertical"
android:overScrollMode="ifContentScrolls"
app:layout_constraintTop_toTopOf="parent" />
@ -88,4 +88,15 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<org.thoughtcrime.securesms.components.menu.SignalBottomActionBar
android:id="@+id/conversation_bottom_action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:title="@string/conversation_context__menu_message_details"
android:id="@+id/menu_context_details"
android:icon="@drawable/ic_info_white_24"
app:iconTint="@color/signal_icon_tint_primary"
app:showAsAction="always" />
<item android:title="@string/conversation_context__menu_delete_message"
android:id="@+id/menu_context_delete_message"
android:icon="@drawable/ic_trash_24"
app:iconTint="@color/signal_icon_tint_primary"
app:showAsAction="always" />
<item android:title="@string/conversation_context__menu_copy_text"
android:id="@+id/menu_context_copy"
android:icon="@drawable/ic_copy_24"
app:iconTint="@color/signal_icon_tint_primary"
app:showAsAction="always" />
<item android:title="@string/conversation_context__menu_reply_to_message"
android:id="@+id/menu_context_reply"
android:visible="true"
android:icon="@drawable/ic_reply_24"
app:iconTint="@color/signal_icon_tint_primary"
app:showAsAction="always" />
<item android:title="@string/conversation_context__menu_resend_message"
android:id="@+id/menu_context_resend"
android:visible="false"
app:showAsAction="never" />
<item android:title="@string/conversation_context_image__save_attachment"
android:id="@+id/menu_context_save_attachment"
android:visible="false"
android:icon="@drawable/ic_save_24"
app:iconTint="@color/signal_icon_tint_primary"
app:showAsAction="always" />
<item android:title="@string/conversation_context__menu_forward_message"
android:id="@+id/menu_context_forward"
android:icon="@drawable/ic_forward_24"
app:iconTint="@color/signal_icon_tint_primary"
app:showAsAction="always" />
</menu>

View file

@ -69,6 +69,7 @@
<dimen name="thumbnail_default_radius">4dp</dimen>
<dimen name="conversation_bottom_padding">2dp</dimen>
<dimen name="conversation_compose_height">40dp</dimen>
<dimen name="conversation_individual_right_gutter">16dp</dimen>
<dimen name="conversation_individual_left_gutter">16dp</dimen>

View file

@ -2785,13 +2785,22 @@
<string name="conversation_context__menu_forward_message">Forward</string>
<!-- Button to retry sending a message -->
<string name="conversation_context__menu_resend_message">Resend message</string>
<string name="conversation_context__menu_reply_to_message">Reply to message</string>
<!-- Button to reply to a message -->
<string name="conversation_context__menu_reply_to_message">Reply</string>
<!-- conversation_context_reacction -->
<!-- conversation_context_reaction -->
<!-- Button to select a message and enter selection mode -->
<string name="conversation_context__reaction_multi_select">Select multiple</string>
<!-- Heading which shows how many messages are currently selected -->
<plurals name="conversation_context__s_selected">
<item quantity="one">%d selected</item>
<item quantity="other">%d selected</item>
</plurals>
<!-- conversation_context_image -->
<string name="conversation_context_image__save_attachment">Save attachment</string>
<!-- Button to save a message attachment (image, file etc.) -->
<string name="conversation_context_image__save_attachment">Save</string>
<!-- conversation_expiring_off -->
<string name="conversation_expiring_off__disappearing_messages">Disappearing messages</string>