From 3e5be2cfe2468beae07d6454d12c13ed4499fa42 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Tue, 19 Oct 2021 11:06:32 -0400 Subject: [PATCH] Show a popup menu when long-pressing on the conversation list. --- .../securesms/badges/BadgeImageView.kt | 2 - .../securesms/components/SignalContextMenu.kt | 204 ++++++++++++++++++ .../ConversationListAdapter.java | 6 +- .../ConversationListFragment.java | 129 +++++++---- .../securesms/database/ThreadDatabase.java | 2 +- .../main/res/drawable-night/ic_archive_24.xml | 10 + .../main/res/drawable-night/ic_delete_24.xml | 9 + app/src/main/res/drawable-night/ic_pin_24.xml | 16 +- .../main/res/drawable-night/ic_read_24.xml | 9 + .../main/res/drawable-night/ic_select_24.xml | 9 + .../res/drawable-night/ic_unarchive_24.xml | 10 + .../main/res/drawable-night/ic_unpin_24.xml | 15 +- .../main/res/drawable-night/ic_unread_24.xml | 12 ++ ...al_context_menu_item_background_bottom.xml | 15 ++ ...al_context_menu_item_background_middle.xml | 15 ++ ...gnal_context_menu_item_background_only.xml | 15 ++ ...ignal_context_menu_item_background_top.xml | 15 ++ app/src/main/res/drawable/ic_archive_24.xml | 10 + app/src/main/res/drawable/ic_delete_24.xml | 9 + app/src/main/res/drawable/ic_pin_24.xml | 11 +- app/src/main/res/drawable/ic_read_24.xml | 11 + app/src/main/res/drawable/ic_select_24.xml | 12 +- app/src/main/res/drawable/ic_unarchive_24.xml | 21 ++ app/src/main/res/drawable/ic_unpin_24.xml | 15 +- app/src/main/res/drawable/ic_unread_24.xml | 10 + .../signal_context_menu_background.xml | 9 + .../main/res/layout/signal_context_menu.xml | 7 + .../res/layout/signal_context_menu_item.xml | 30 +++ app/src/main/res/values/dimens.xml | 2 + app/src/main/res/values/strings.xml | 10 + 30 files changed, 554 insertions(+), 96 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/SignalContextMenu.kt create mode 100644 app/src/main/res/drawable-night/ic_archive_24.xml create mode 100644 app/src/main/res/drawable-night/ic_delete_24.xml create mode 100644 app/src/main/res/drawable-night/ic_read_24.xml create mode 100644 app/src/main/res/drawable-night/ic_select_24.xml create mode 100644 app/src/main/res/drawable-night/ic_unarchive_24.xml create mode 100644 app/src/main/res/drawable-night/ic_unread_24.xml create mode 100644 app/src/main/res/drawable-v21/signal_context_menu_item_background_bottom.xml create mode 100644 app/src/main/res/drawable-v21/signal_context_menu_item_background_middle.xml create mode 100644 app/src/main/res/drawable-v21/signal_context_menu_item_background_only.xml create mode 100644 app/src/main/res/drawable-v21/signal_context_menu_item_background_top.xml create mode 100644 app/src/main/res/drawable/ic_archive_24.xml create mode 100644 app/src/main/res/drawable/ic_delete_24.xml create mode 100644 app/src/main/res/drawable/ic_read_24.xml create mode 100644 app/src/main/res/drawable/ic_unarchive_24.xml create mode 100644 app/src/main/res/drawable/ic_unread_24.xml create mode 100644 app/src/main/res/drawable/signal_context_menu_background.xml create mode 100644 app/src/main/res/layout/signal_context_menu.xml create mode 100644 app/src/main/res/layout/signal_context_menu_item.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/BadgeImageView.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/BadgeImageView.kt index b59724793..8d1e583b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/badges/BadgeImageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/badges/BadgeImageView.kt @@ -4,7 +4,6 @@ import android.content.Context import android.util.AttributeSet import androidx.appcompat.widget.AppCompatImageView import androidx.core.content.res.use -import androidx.lifecycle.Lifecycle import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R @@ -13,7 +12,6 @@ import org.thoughtcrime.securesms.badges.models.Badge import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.ThemeUtil -import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.visible import java.lang.IllegalArgumentException diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/SignalContextMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/components/SignalContextMenu.kt new file mode 100644 index 000000000..dcfec0476 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/SignalContextMenu.kt @@ -0,0 +1,204 @@ +package org.thoughtcrime.securesms.components + +import android.content.Context +import android.os.Build +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.PopupWindow +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.util.MappingAdapter +import org.thoughtcrime.securesms.util.MappingModel +import org.thoughtcrime.securesms.util.MappingViewHolder + +/** + * A custom context menu that will show next to an anchor view and display several options. Basically a PopupMenu with custom UI and positioning rules. + * + * This will prefer showing the menu underneath the anchor, but if there's not enough space in the container, it will show it above the anchor and reverse the + * order of the menu items. If there's not enough room for either, it'll show it centered above the anchor. If there's not enough room then, it'll center it, + * chop off the part that doesn't fit, and make the menu scrollable. + */ +class SignalContextMenu private constructor( + val anchor: View, + val container: View, + val items: List, + val baseOffsetX: Int = 0, + val baseOffsetY: Int = 0, + val onDismiss: Runnable? = null +) : PopupWindow( + LayoutInflater.from(anchor.context).inflate(R.layout.signal_context_menu, null), + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT +) { + + val context: Context = anchor.context + + val mappingAdapter = MappingAdapter().apply { + registerFactory(DisplayItem::class.java, ItemViewHolderFactory()) + } + + init { + setBackgroundDrawable(ContextCompat.getDrawable(context, R.drawable.signal_context_menu_background)) + + isFocusable = true + + if (onDismiss != null) { + setOnDismissListener { onDismiss.run() } + } + + if (Build.VERSION.SDK_INT >= 21) { + elevation = 20f + } + + contentView.findViewById(R.id.signal_context_menu_list).apply { + adapter = mappingAdapter + layoutManager = LinearLayoutManager(context) + itemAnimator = null + } + + mappingAdapter.submitList(items.toAdapterItems()) + } + + private fun show() { + if (anchor.width == 0 || anchor.height == 0) { + anchor.post(this::show) + return + } + + contentView.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + + val menuBottomBound = anchor.y + anchor.height + contentView.measuredHeight + baseOffsetY + val menuTopBound = anchor.y - contentView.measuredHeight - baseOffsetY + + val screenBottomBound = container.height + val screenTopBound = container.y + + val offsetY: Int + + if (menuBottomBound < screenBottomBound) { + offsetY = baseOffsetY + } else if (menuTopBound > screenTopBound) { + offsetY = -(anchor.height + contentView.measuredHeight + baseOffsetY) + mappingAdapter.submitList(items.reversed().toAdapterItems()) + } else { + offsetY = -((anchor.height / 2) + (contentView.measuredHeight / 2) + baseOffsetY) + } + + showAsDropDown(anchor, baseOffsetX, offsetY) + } + + private fun List.toAdapterItems(): List { + return this.mapIndexed { index, item -> + val displayType: DisplayType = when { + this.size == 1 -> DisplayType.ONLY + index == 0 -> DisplayType.TOP + index == this.size - 1 -> DisplayType.BOTTOM + else -> DisplayType.MIDDLE + } + + DisplayItem(item, displayType) + } + } + + data class Item( + @DrawableRes val iconRes: Int, + @StringRes val titleRes: Int, + val action: Runnable + ) + + private data class DisplayItem( + val item: Item, + val displayType: DisplayType + ) : MappingModel { + override fun areItemsTheSame(newItem: DisplayItem): Boolean { + return this == newItem + } + + override fun areContentsTheSame(newItem: DisplayItem): Boolean { + return this == newItem + } + } + + enum class DisplayType { + TOP, BOTTOM, MIDDLE, ONLY + } + + private inner class ItemViewHolder(itemView: View) : MappingViewHolder(itemView) { + val icon: ImageView = itemView.findViewById(R.id.signal_context_menu_item_icon) + val title: TextView = itemView.findViewById(R.id.signal_context_menu_item_title) + + override fun bind(model: DisplayItem) { + icon.setImageResource(model.item.iconRes) + title.setText(model.item.titleRes) + itemView.setOnClickListener { + model.item.action.run() + dismiss() + } + + if (Build.VERSION.SDK_INT >= 21) { + when (model.displayType) { + DisplayType.TOP -> itemView.setBackgroundResource(R.drawable.signal_context_menu_item_background_top) + DisplayType.BOTTOM -> itemView.setBackgroundResource(R.drawable.signal_context_menu_item_background_bottom) + DisplayType.MIDDLE -> itemView.setBackgroundResource(R.drawable.signal_context_menu_item_background_middle) + DisplayType.ONLY -> itemView.setBackgroundResource(R.drawable.signal_context_menu_item_background_only) + } + } + } + } + + private inner class ItemViewHolderFactory : MappingAdapter.Factory { + override fun createViewHolder(parent: ViewGroup): MappingViewHolder { + return ItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.signal_context_menu_item, parent, false)) + } + } + + /** + * @param anchor The view to put the pop-up on + * @param container A parent of [anchor] that represents the acceptable boundaries of the popup + */ + class Builder( + val anchor: View, + val container: View + ) { + + var onDismiss: Runnable? = null + var offsetX: Int = 0 + var offsetY: Int = 0 + + fun onDismiss(onDismiss: Runnable): Builder { + this.onDismiss = onDismiss + return this + } + + fun offsetX(offsetPx: Int): Builder { + this.offsetX = offsetPx + return this + } + + fun offsetY(offsetPx: Int): Builder { + this.offsetY = offsetPx + return this + } + + fun show(items: List) { + SignalContextMenu( + anchor = anchor, + container = container, + items = items, + baseOffsetX = offsetX, + baseOffsetY = offsetY, + onDismiss = onDismiss + ).show() + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListAdapter.java index f1e5fdf4c..94e568371 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListAdapter.java @@ -90,7 +90,7 @@ class ConversationListAdapter extends ListAdapter selectedConversations = new HashSet<>(defaultAdapter.getBatchSelectionIds()); + private void handleMarkAsRead(@NonNull Collection ids) { + Context context = requireContext(); SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> { - List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(selectedConversations, false); + List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(ids, false); ApplicationDependencies.getMessageNotifier().updateNotification(context); MarkReadReceiver.process(context, messageIds); @@ -781,12 +784,11 @@ public class ConversationListFragment extends MainFragment implements ActionMode }); } - private void handleMarkSelectedAsUnread() { - Context context = requireContext(); - Set selectedConversations = new HashSet<>(defaultAdapter.getBatchSelectionIds()); + private void handleMarkAsUnread(@NonNull Collection ids) { + Context context = requireContext(); SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> { - DatabaseFactory.getThreadDatabase(context).setForcedUnread(selectedConversations); + DatabaseFactory.getThreadDatabase(context).setForcedUnread(ids); StorageSyncHelper.scheduleSyncForDataChange(); return null; }, none -> { @@ -806,8 +808,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode } @SuppressLint("StaticFieldLeak") - private void handleArchiveAllSelected() { - Set selectedConversations = new HashSet<>(defaultAdapter.getBatchSelectionIds()); + private void handleArchive(@NonNull Collection ids, boolean showProgress) { + Set selectedConversations = new HashSet<>(ids); int count = selectedConversations.size(); String snackBarTitle = getResources().getQuantityString(getArchivedSnackbarTitleRes(), count, count); @@ -816,7 +818,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode snackBarTitle, getString(R.string.ConversationListFragment_undo), getResources().getColor(R.color.amber_500), - Snackbar.LENGTH_LONG, true) + Snackbar.LENGTH_LONG, + showProgress) { @Override @@ -838,22 +841,23 @@ public class ConversationListFragment extends MainFragment implements ActionMode protected void reverseAction(@Nullable Void parameter) { reverseArchiveThreads(selectedConversations); } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + }.executeOnExecutor(SignalExecutors.BOUNDED); } @SuppressLint("StaticFieldLeak") - private void handleDeleteAllSelected() { - int conversationsCount = defaultAdapter.getBatchSelectionIds().size(); - AlertDialog.Builder alert = new AlertDialog.Builder(getActivity()); - alert.setIcon(R.drawable.ic_warning); - alert.setTitle(getActivity().getResources().getQuantityString(R.plurals.ConversationListFragment_delete_selected_conversations, - conversationsCount, conversationsCount)); - alert.setMessage(getActivity().getResources().getQuantityString(R.plurals.ConversationListFragment_this_will_permanently_delete_all_n_selected_conversations, - conversationsCount, conversationsCount)); + private void handleDelete(@NonNull Collection ids) { + int conversationsCount = ids.size(); + MaterialAlertDialogBuilder alert = new MaterialAlertDialogBuilder(requireActivity()); + Context context = requireContext(); + + alert.setTitle(context.getResources().getQuantityString(R.plurals.ConversationListFragment_delete_selected_conversations, + conversationsCount, conversationsCount)); + alert.setMessage(context.getResources().getQuantityString(R.plurals.ConversationListFragment_this_will_permanently_delete_all_n_selected_conversations, + conversationsCount, conversationsCount)); alert.setCancelable(true); alert.setPositiveButton(R.string.delete, (dialog, which) -> { - final Set selectedConversations = new HashSet<>(defaultAdapter.getBatchSelectionIds()); + final Set selectedConversations = new HashSet<>(ids); if (!selectedConversations.isEmpty()) { new AsyncTask() { @@ -861,16 +865,16 @@ public class ConversationListFragment extends MainFragment implements ActionMode @Override protected void onPreExecute() { - dialog = ProgressDialog.show(getActivity(), - getActivity().getString(R.string.ConversationListFragment_deleting), - getActivity().getString(R.string.ConversationListFragment_deleting_selected_conversations), + dialog = ProgressDialog.show(requireActivity(), + context.getString(R.string.ConversationListFragment_deleting), + context.getString(R.string.ConversationListFragment_deleting_selected_conversations), true, false); } @Override protected Void doInBackground(Void... params) { DatabaseFactory.getThreadDatabase(getActivity()).deleteConversations(selectedConversations); - ApplicationDependencies.getMessageNotifier().updateNotification(getActivity()); + ApplicationDependencies.getMessageNotifier().updateNotification(requireActivity()); return null; } @@ -882,7 +886,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode actionMode = null; } } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + }.executeOnExecutor(SignalExecutors.BOUNDED); } }); @@ -890,8 +894,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode alert.show(); } - private void handlePinAllSelected() { - final Set toPin = new LinkedHashSet<>(Stream.of(defaultAdapter.getBatchSelection()) + private void handlePin(@NonNull Collection conversations) { + final Set toPin = new LinkedHashSet<>(Stream.of(conversations) .filterNot(conversation -> conversation.getThreadRecord().isPinned()) .map(conversation -> conversation.getThreadRecord().getThreadId()) .toList()); @@ -902,7 +906,9 @@ public class ConversationListFragment extends MainFragment implements ActionMode Snackbar.LENGTH_LONG) .setTextColor(Color.WHITE) .show(); - actionMode.finish(); + if (actionMode != null) { + actionMode.finish(); + } return; } @@ -919,13 +925,11 @@ public class ConversationListFragment extends MainFragment implements ActionMode }); } - private void handleUnpinAllSelected() { - final Set toPin = new HashSet<>(defaultAdapter.getBatchSelectionIds()); - + private void handleUnpin(@NonNull Collection ids) { SimpleTask.run(SignalExecutors.BOUNDED, () -> { ThreadDatabase db = DatabaseFactory.getThreadDatabase(ApplicationDependencies.getApplication()); - db.unpinConversations(toPin); + db.unpinConversations(ids); return null; }, unused -> { @@ -1005,7 +1009,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode } @Override - public void onConversationClick(Conversation conversation) { + public void onConversationClick(@NonNull Conversation conversation) { if (actionMode == null) { handleCreateConversation(conversation.getThreadRecord().getThreadId(), conversation.getThreadRecord().getRecipient(), conversation.getThreadRecord().getDistributionType()); } else { @@ -1021,16 +1025,51 @@ public class ConversationListFragment extends MainFragment implements ActionMode } @Override - public boolean onConversationLongClick(Conversation conversation) { + public boolean onConversationLongClick(@NonNull Conversation conversation, @NonNull View view) { if (actionMode != null) { onConversationClick(conversation); return true; } - defaultAdapter.initializeBatchMode(true); - defaultAdapter.toggleConversationInBatchSet(conversation); + view.setSelected(true); - actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(ConversationListFragment.this); + Collection id = Collections.singleton(conversation.getThreadRecord().getThreadId()); + + List items = new ArrayList<>(); + + if (!conversation.getThreadRecord().isArchived()) { + if (conversation.getThreadRecord().isRead()) { + items.add(new SignalContextMenu.Item(R.drawable.ic_unread_24, R.string.ConversationListFragment_unread, () -> handleMarkAsUnread(id))); + } else { + items.add(new SignalContextMenu.Item(R.drawable.ic_read_24, R.string.ConversationListFragment_read, () -> handleMarkAsRead(id))); + } + + if (conversation.getThreadRecord().isPinned()) { + items.add(new SignalContextMenu.Item(R.drawable.ic_unpin_24, R.string.ConversationListFragment_unpin, () -> handleUnpin(id))); + } else { + items.add(new SignalContextMenu.Item(R.drawable.ic_pin_24, R.string.ConversationListFragment_pin, () -> handlePin(Collections.singleton(conversation)))); + } + } + + items.add(new SignalContextMenu.Item(R.drawable.ic_select_24, R.string.ConversationListFragment_select, () -> { + defaultAdapter.initializeBatchMode(true); + defaultAdapter.toggleConversationInBatchSet(conversation); + actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(ConversationListFragment.this); + })); + + if (conversation.getThreadRecord().isArchived()) { + items.add(new SignalContextMenu.Item(R.drawable.ic_unarchive_24, R.string.ConversationListFragment_unarchive, () -> handleArchive(id, false))); + } else { + items.add(new SignalContextMenu.Item(R.drawable.ic_archive_24, R.string.ConversationListFragment_archive, () -> handleArchive(id, false))); + } + + items.add(new SignalContextMenu.Item(R.drawable.ic_delete_24, R.string.ConversationListFragment_delete, () -> handleDelete(id))); + + new SignalContextMenu.Builder(view, list) + .offsetX(ViewUtil.dpToPx(12)) + .offsetY(ViewUtil.dpToPx(12)) + .onDismiss(() -> view.setSelected(false)) + .show(items); return true; } @@ -1059,13 +1098,13 @@ public class ConversationListFragment extends MainFragment implements ActionMode @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { - case R.id.menu_select_all: handleSelectAllThreads(); return true; - case R.id.menu_delete_selected: handleDeleteAllSelected(); return true; - case R.id.menu_pin_selected: handlePinAllSelected(); return true; - case R.id.menu_unpin_selected: handleUnpinAllSelected(); return true; - case R.id.menu_archive_selected: handleArchiveAllSelected(); return true; - case R.id.menu_mark_as_read: handleMarkSelectedAsRead(); return true; - case R.id.menu_mark_as_unread: handleMarkSelectedAsUnread(); return true; + case R.id.menu_select_all: handleSelectAllThreads(); return true; + case R.id.menu_delete_selected: handleDelete(defaultAdapter.getBatchSelectionIds()); return true; + case R.id.menu_pin_selected: handlePin(defaultAdapter.getBatchSelection()); return true; + case R.id.menu_unpin_selected: handleUnpin(defaultAdapter.getBatchSelectionIds()); return true; + case R.id.menu_archive_selected: handleArchive(defaultAdapter.getBatchSelectionIds(), true); return true; + case R.id.menu_mark_as_read: handleMarkAsRead(defaultAdapter.getBatchSelectionIds()); return true; + case R.id.menu_mark_as_unread: handleMarkAsUnread(defaultAdapter.getBatchSelectionIds()); return true; } return false; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 4824925ca..8f653b0e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -867,7 +867,7 @@ public class ThreadDatabase extends Database { StorageSyncHelper.scheduleSyncForDataChange(); } - public void unpinConversations(@NonNull Set threadIds) { + public void unpinConversations(@NonNull Collection threadIds) { SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); ContentValues contentValues = new ContentValues(1); String placeholders = StringUtil.join(Stream.of(threadIds).map(unused -> "?").toList(), ","); diff --git a/app/src/main/res/drawable-night/ic_archive_24.xml b/app/src/main/res/drawable-night/ic_archive_24.xml new file mode 100644 index 000000000..59a8baa41 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_archive_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable-night/ic_delete_24.xml b/app/src/main/res/drawable-night/ic_delete_24.xml new file mode 100644 index 000000000..c255646ce --- /dev/null +++ b/app/src/main/res/drawable-night/ic_delete_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-night/ic_pin_24.xml b/app/src/main/res/drawable-night/ic_pin_24.xml index 4d7ad3575..9fddfcc62 100644 --- a/app/src/main/res/drawable-night/ic_pin_24.xml +++ b/app/src/main/res/drawable-night/ic_pin_24.xml @@ -3,16 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - - - - - - + diff --git a/app/src/main/res/drawable-night/ic_read_24.xml b/app/src/main/res/drawable-night/ic_read_24.xml new file mode 100644 index 000000000..468a21da6 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_read_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-night/ic_select_24.xml b/app/src/main/res/drawable-night/ic_select_24.xml new file mode 100644 index 000000000..d36a2d515 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_select_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-night/ic_unarchive_24.xml b/app/src/main/res/drawable-night/ic_unarchive_24.xml new file mode 100644 index 000000000..f8c34610d --- /dev/null +++ b/app/src/main/res/drawable-night/ic_unarchive_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable-night/ic_unpin_24.xml b/app/src/main/res/drawable-night/ic_unpin_24.xml index 8703936b6..96a1e15af 100644 --- a/app/src/main/res/drawable-night/ic_unpin_24.xml +++ b/app/src/main/res/drawable-night/ic_unpin_24.xml @@ -3,15 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - - - - + diff --git a/app/src/main/res/drawable-night/ic_unread_24.xml b/app/src/main/res/drawable-night/ic_unread_24.xml new file mode 100644 index 000000000..38e57ad92 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_unread_24.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable-v21/signal_context_menu_item_background_bottom.xml b/app/src/main/res/drawable-v21/signal_context_menu_item_background_bottom.xml new file mode 100644 index 000000000..c4faad258 --- /dev/null +++ b/app/src/main/res/drawable-v21/signal_context_menu_item_background_bottom.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/signal_context_menu_item_background_middle.xml b/app/src/main/res/drawable-v21/signal_context_menu_item_background_middle.xml new file mode 100644 index 000000000..ed5861758 --- /dev/null +++ b/app/src/main/res/drawable-v21/signal_context_menu_item_background_middle.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/signal_context_menu_item_background_only.xml b/app/src/main/res/drawable-v21/signal_context_menu_item_background_only.xml new file mode 100644 index 000000000..38d2da254 --- /dev/null +++ b/app/src/main/res/drawable-v21/signal_context_menu_item_background_only.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/signal_context_menu_item_background_top.xml b/app/src/main/res/drawable-v21/signal_context_menu_item_background_top.xml new file mode 100644 index 000000000..d9cd763e8 --- /dev/null +++ b/app/src/main/res/drawable-v21/signal_context_menu_item_background_top.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_archive_24.xml b/app/src/main/res/drawable/ic_archive_24.xml new file mode 100644 index 000000000..49f77523c --- /dev/null +++ b/app/src/main/res/drawable/ic_archive_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_delete_24.xml b/app/src/main/res/drawable/ic_delete_24.xml new file mode 100644 index 000000000..e26e02a2c --- /dev/null +++ b/app/src/main/res/drawable/ic_delete_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_pin_24.xml b/app/src/main/res/drawable/ic_pin_24.xml index 170b0b3e5..9518551d5 100644 --- a/app/src/main/res/drawable/ic_pin_24.xml +++ b/app/src/main/res/drawable/ic_pin_24.xml @@ -3,11 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - - - + diff --git a/app/src/main/res/drawable/ic_read_24.xml b/app/src/main/res/drawable/ic_read_24.xml new file mode 100644 index 000000000..2bbc15b7a --- /dev/null +++ b/app/src/main/res/drawable/ic_read_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_select_24.xml b/app/src/main/res/drawable/ic_select_24.xml index 8520b0ac8..f074f1a2d 100644 --- a/app/src/main/res/drawable/ic_select_24.xml +++ b/app/src/main/res/drawable/ic_select_24.xml @@ -1,5 +1,9 @@ - - + + diff --git a/app/src/main/res/drawable/ic_unarchive_24.xml b/app/src/main/res/drawable/ic_unarchive_24.xml new file mode 100644 index 000000000..4d597711f --- /dev/null +++ b/app/src/main/res/drawable/ic_unarchive_24.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_unpin_24.xml b/app/src/main/res/drawable/ic_unpin_24.xml index ea7309066..d3f315968 100644 --- a/app/src/main/res/drawable/ic_unpin_24.xml +++ b/app/src/main/res/drawable/ic_unpin_24.xml @@ -3,15 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - - - - + diff --git a/app/src/main/res/drawable/ic_unread_24.xml b/app/src/main/res/drawable/ic_unread_24.xml new file mode 100644 index 000000000..dacebb8a5 --- /dev/null +++ b/app/src/main/res/drawable/ic_unread_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/signal_context_menu_background.xml b/app/src/main/res/drawable/signal_context_menu_background.xml new file mode 100644 index 000000000..e66e27557 --- /dev/null +++ b/app/src/main/res/drawable/signal_context_menu_background.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/signal_context_menu.xml b/app/src/main/res/layout/signal_context_menu.xml new file mode 100644 index 000000000..d875363a9 --- /dev/null +++ b/app/src/main/res/layout/signal_context_menu.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/signal_context_menu_item.xml b/app/src/main/res/layout/signal_context_menu_item.xml new file mode 100644 index 000000000..e890f3597 --- /dev/null +++ b/app/src/main/res/layout/signal_context_menu_item.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 0dce482e3..083b833d5 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -209,4 +209,6 @@ 16dp 3 + + 18dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 83cbce9fd..629aa9c96 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -367,6 +367,16 @@ Moved conversation to inbox Moved %d conversations to inbox + Read + Unread + Pin + Unpin + Mute + Unmute + Select + Archive + Unarchive + Delete Key exchange message