mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Add new bottom actionbar to chat list.
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package org.thoughtcrime.securesms.components.menu
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
/**
|
||||
* Represents an action to be rendered via [SignalContextMenu] or [SignalBottomActionBar]
|
||||
*/
|
||||
data class ActionItem(
|
||||
@DrawableRes val iconRes: Int,
|
||||
@StringRes val titleRes: Int,
|
||||
val action: Runnable
|
||||
)
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.thoughtcrime.securesms.components.menu
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
|
||||
/**
|
||||
* A bar that displays a set of action buttons. Intended as a replacement for ActionModes, this gives you a simple interface to add a bunch of actions, and
|
||||
* the bar itself will handle putting things in the overflow and whatnot.
|
||||
*
|
||||
* Overflow items are rendered in a [SignalContextMenu].
|
||||
*/
|
||||
class SignalBottomActionBar(context: Context, attributeSet: AttributeSet) : LinearLayout(context, attributeSet) {
|
||||
|
||||
val items: MutableList<ActionItem> = mutableListOf()
|
||||
|
||||
init {
|
||||
orientation = HORIZONTAL
|
||||
setBackgroundResource(R.drawable.signal_bottom_action_bar_background)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
elevation = 20f
|
||||
}
|
||||
}
|
||||
|
||||
fun setItems(items: List<ActionItem>) {
|
||||
this.items.clear()
|
||||
this.items.addAll(items)
|
||||
present(this.items)
|
||||
}
|
||||
|
||||
private fun present(items: List<ActionItem>) {
|
||||
if (width == 0) {
|
||||
post { present(items) }
|
||||
return
|
||||
}
|
||||
|
||||
val widthDp: Float = ViewUtil.pxToDp(width.toFloat())
|
||||
val minButtonWidthDp = 70
|
||||
val maxButtons: Int = (widthDp / minButtonWidthDp).toInt()
|
||||
val usableButtonCount = when {
|
||||
items.size <= maxButtons -> items.size
|
||||
else -> maxButtons - 1
|
||||
}
|
||||
|
||||
val renderableItems: List<ActionItem> = items.subList(0, usableButtonCount)
|
||||
val overflowItems: List<ActionItem> = if (renderableItems.size < items.size) items.subList(usableButtonCount, items.size) else emptyList()
|
||||
|
||||
removeAllViews()
|
||||
|
||||
renderableItems.forEach { item ->
|
||||
val view: View = LayoutInflater.from(context).inflate(R.layout.signal_bottom_action_bar_item, this, false)
|
||||
addView(view)
|
||||
bindItem(view, item)
|
||||
}
|
||||
|
||||
if (overflowItems.isNotEmpty()) {
|
||||
val view: View = LayoutInflater.from(context).inflate(R.layout.signal_bottom_action_bar_item, this, false)
|
||||
addView(view)
|
||||
bindItem(
|
||||
view,
|
||||
ActionItem(
|
||||
iconRes = R.drawable.ic_more_horiz_24,
|
||||
titleRes = R.string.SignalBottomActionBar_more,
|
||||
action = {
|
||||
SignalContextMenu.Builder(view, parent as ViewGroup)
|
||||
.preferredHorizontalPosition(SignalContextMenu.HorizontalPosition.END)
|
||||
.offsetY(ViewUtil.dpToPx(8))
|
||||
.show(overflowItems)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindItem(view: View, item: ActionItem) {
|
||||
val icon: ImageView = view.findViewById(R.id.signal_bottom_action_bar_item_icon)
|
||||
val title: TextView = view.findViewById(R.id.signal_bottom_action_bar_item_title)
|
||||
|
||||
icon.setImageResource(item.iconRes)
|
||||
title.setText(item.titleRes)
|
||||
view.setOnClickListener { item.action.run() }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.components
|
||||
package org.thoughtcrime.securesms.components.menu
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -8,8 +9,6 @@ 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
|
||||
@@ -17,6 +16,7 @@ import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.MappingModel
|
||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -27,10 +27,11 @@ import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||
*/
|
||||
class SignalContextMenu private constructor(
|
||||
val anchor: View,
|
||||
val container: View,
|
||||
val items: List<Item>,
|
||||
val container: ViewGroup,
|
||||
val items: List<ActionItem>,
|
||||
val baseOffsetX: Int = 0,
|
||||
val baseOffsetY: Int = 0,
|
||||
val horizontalPosition: HorizontalPosition = HorizontalPosition.START,
|
||||
val onDismiss: Runnable? = null
|
||||
) : PopupWindow(
|
||||
LayoutInflater.from(anchor.context).inflate(R.layout.signal_context_menu, null),
|
||||
@@ -77,8 +78,14 @@ class SignalContextMenu private constructor(
|
||||
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
||||
)
|
||||
|
||||
val menuBottomBound = anchor.y + anchor.height + contentView.measuredHeight + baseOffsetY
|
||||
val menuTopBound = anchor.y - contentView.measuredHeight - baseOffsetY
|
||||
val anchorRect = Rect(anchor.left, anchor.top, anchor.right, anchor.bottom).also {
|
||||
if (anchor.parent != container) {
|
||||
container.offsetDescendantRectToMyCoords(anchor, it)
|
||||
}
|
||||
}
|
||||
|
||||
val menuBottomBound = anchorRect.bottom + contentView.measuredHeight + baseOffsetY
|
||||
val menuTopBound = anchorRect.top - contentView.measuredHeight - baseOffsetY
|
||||
|
||||
val screenBottomBound = container.height
|
||||
val screenTopBound = container.y
|
||||
@@ -88,16 +95,33 @@ class SignalContextMenu private constructor(
|
||||
if (menuBottomBound < screenBottomBound) {
|
||||
offsetY = baseOffsetY
|
||||
} else if (menuTopBound > screenTopBound) {
|
||||
offsetY = -(anchor.height + contentView.measuredHeight + baseOffsetY)
|
||||
offsetY = -(anchorRect.height() + contentView.measuredHeight + baseOffsetY)
|
||||
mappingAdapter.submitList(items.reversed().toAdapterItems())
|
||||
} else {
|
||||
offsetY = -((anchor.height / 2) + (contentView.measuredHeight / 2) + baseOffsetY)
|
||||
offsetY = -((anchorRect.height() / 2) + (contentView.measuredHeight / 2) + baseOffsetY)
|
||||
}
|
||||
|
||||
showAsDropDown(anchor, baseOffsetX, offsetY)
|
||||
val offsetX: Int = when (horizontalPosition) {
|
||||
HorizontalPosition.START -> {
|
||||
if (ViewUtil.isLtr(context)) {
|
||||
baseOffsetX
|
||||
} else {
|
||||
-(baseOffsetX + contentView.measuredWidth)
|
||||
}
|
||||
}
|
||||
HorizontalPosition.END -> {
|
||||
if (ViewUtil.isLtr(context)) {
|
||||
-(baseOffsetX + contentView.measuredWidth - anchorRect.width())
|
||||
} else {
|
||||
baseOffsetX - anchorRect.width()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showAsDropDown(anchor, offsetX, offsetY)
|
||||
}
|
||||
|
||||
private fun List<Item>.toAdapterItems(): List<DisplayItem> {
|
||||
private fun List<ActionItem>.toAdapterItems(): List<DisplayItem> {
|
||||
return this.mapIndexed { index, item ->
|
||||
val displayType: DisplayType = when {
|
||||
this.size == 1 -> DisplayType.ONLY
|
||||
@@ -110,14 +134,8 @@ class SignalContextMenu private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
data class Item(
|
||||
@DrawableRes val iconRes: Int,
|
||||
@StringRes val titleRes: Int,
|
||||
val action: Runnable
|
||||
)
|
||||
|
||||
private data class DisplayItem(
|
||||
val item: Item,
|
||||
val item: ActionItem,
|
||||
val displayType: DisplayType
|
||||
) : MappingModel<DisplayItem> {
|
||||
override fun areItemsTheSame(newItem: DisplayItem): Boolean {
|
||||
@@ -129,7 +147,7 @@ class SignalContextMenu private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
enum class DisplayType {
|
||||
private enum class DisplayType {
|
||||
TOP, BOTTOM, MIDDLE, ONLY
|
||||
}
|
||||
|
||||
@@ -162,18 +180,23 @@ class SignalContextMenu private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
enum class HorizontalPosition {
|
||||
START, END
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
val container: ViewGroup
|
||||
) {
|
||||
|
||||
var onDismiss: Runnable? = null
|
||||
var offsetX: Int = 0
|
||||
var offsetY: Int = 0
|
||||
var offsetX = 0
|
||||
var offsetY = 0
|
||||
var horizontalPosition = HorizontalPosition.START
|
||||
|
||||
fun onDismiss(onDismiss: Runnable): Builder {
|
||||
this.onDismiss = onDismiss
|
||||
@@ -190,13 +213,19 @@ class SignalContextMenu private constructor(
|
||||
return this
|
||||
}
|
||||
|
||||
fun show(items: List<Item>) {
|
||||
fun preferredHorizontalPosition(horizontalPosition: HorizontalPosition): Builder {
|
||||
this.horizontalPosition = horizontalPosition
|
||||
return this
|
||||
}
|
||||
|
||||
fun show(items: List<ActionItem>) {
|
||||
SignalContextMenu(
|
||||
anchor = anchor,
|
||||
container = container,
|
||||
items = items,
|
||||
baseOffsetX = offsetX,
|
||||
baseOffsetY = offsetY,
|
||||
horizontalPosition = horizontalPosition,
|
||||
onDismiss = onDismiss
|
||||
).show()
|
||||
}
|
||||
@@ -1855,8 +1855,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
||||
|
||||
private class ActionModeCallback implements ActionMode.Callback {
|
||||
|
||||
private int statusBarColor;
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
MenuInflater inflater = mode.getMenuInflater();
|
||||
@@ -1864,16 +1862,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
||||
|
||||
mode.setTitle(calculateSelectedItemCount());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
Window window = getActivity().getWindow();
|
||||
statusBarColor = window.getStatusBarColor();
|
||||
WindowUtil.setStatusBarColor(window, getResources().getColor(R.color.action_mode_status_bar));
|
||||
}
|
||||
|
||||
if (!ThemeUtil.isDarkTheme(getContext())) {
|
||||
WindowUtil.setLightStatusBar(getActivity().getWindow());
|
||||
}
|
||||
|
||||
setCorrectActionModeMenuVisibility(menu);
|
||||
listener.onMessageActionToolbarOpened();
|
||||
return true;
|
||||
@@ -1888,12 +1876,6 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
((ConversationAdapter)list.getAdapter()).clearSelection();
|
||||
list.invalidateItemDecorations();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
WindowUtil.setStatusBarColor(requireActivity().getWindow(), statusBarColor);
|
||||
}
|
||||
|
||||
WindowUtil.setLightStatusBarFromTheme(requireActivity());
|
||||
actionMode = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,11 +104,6 @@ public class ConversationListArchiveFragment extends ConversationListFragment im
|
||||
return R.plurals.ConversationListFragment_moved_conversations_to_inbox;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @MenuRes int getActionModeMenuRes() {
|
||||
return R.menu.conversation_list_batch_unarchive;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @DrawableRes int getArchiveIconRes() {
|
||||
return R.drawable.ic_unarchive_white_36dp;
|
||||
|
||||
@@ -45,12 +45,10 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.MenuRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.PluralsRes;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
@@ -80,7 +78,9 @@ import org.thoughtcrime.securesms.NewConversationActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.RatingManager;
|
||||
import org.thoughtcrime.securesms.components.SearchToolbar;
|
||||
import org.thoughtcrime.securesms.components.SignalContextMenu;
|
||||
import org.thoughtcrime.securesms.components.menu.ActionItem;
|
||||
import org.thoughtcrime.securesms.components.menu.SignalBottomActionBar;
|
||||
import org.thoughtcrime.securesms.components.menu.SignalContextMenu;
|
||||
import org.thoughtcrime.securesms.components.UnreadPaymentsView;
|
||||
import org.thoughtcrime.securesms.components.recyclerview.DeleteItemAnimator;
|
||||
import org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton;
|
||||
@@ -148,6 +148,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -200,6 +201,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
private VoiceNoteMediaControllerOwner mediaControllerOwner;
|
||||
private Stub<FrameLayout> voiceNotePlayerViewStub;
|
||||
private VoiceNotePlayerView voiceNotePlayerView;
|
||||
private SignalBottomActionBar bottomActionBar;
|
||||
|
||||
|
||||
private Stopwatch startupStopwatch;
|
||||
@@ -242,6 +244,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
toolbarShadow = view.findViewById(R.id.conversation_list_toolbar_shadow);
|
||||
proxyStatus = view.findViewById(R.id.conversation_list_proxy_status);
|
||||
unreadPaymentsDot = view.findViewById(R.id.unread_payments_indicator);
|
||||
bottomActionBar = view.findViewById(R.id.conversation_list_bottom_action_bar);
|
||||
reminderView = new Stub<>(view.findViewById(R.id.reminder));
|
||||
emptyState = new Stub<>(view.findViewById(R.id.empty_state));
|
||||
searchToolbar = new Stub<>(view.findViewById(R.id.search_toolbar));
|
||||
@@ -777,10 +780,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
return null;
|
||||
}, none -> {
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
endActionModeIfActive();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -792,10 +792,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
StorageSyncHelper.scheduleSyncForDataChange();
|
||||
return null;
|
||||
}, none -> {
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
endActionModeIfActive();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -825,11 +822,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
endActionModeIfActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -881,10 +874,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
dialog.dismiss();
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
}
|
||||
endActionModeIfActive();
|
||||
}
|
||||
}.executeOnExecutor(SignalExecutors.BOUNDED);
|
||||
}
|
||||
@@ -906,9 +896,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setTextColor(Color.WHITE)
|
||||
.show();
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
}
|
||||
endActionModeIfActive();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -919,9 +907,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
return null;
|
||||
}, unused -> {
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
}
|
||||
endActionModeIfActive();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -933,21 +919,40 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
return null;
|
||||
}, unused -> {
|
||||
if (actionMode != null) {
|
||||
actionMode.finish();
|
||||
}
|
||||
endActionModeIfActive();
|
||||
});
|
||||
}
|
||||
|
||||
private void handleSelectAllThreads() {
|
||||
defaultAdapter.selectAllThreads();
|
||||
actionMode.setTitle(String.valueOf(defaultAdapter.getBatchSelectionIds().size()));
|
||||
updateMultiSelectState();
|
||||
}
|
||||
|
||||
private void handleCreateConversation(long threadId, Recipient recipient, int distributionType) {
|
||||
getNavigator().goToConversation(recipient.getId(), threadId, distributionType, -1);
|
||||
}
|
||||
|
||||
private void startActionMode() {
|
||||
actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(ConversationListFragment.this);
|
||||
ViewUtil.fadeIn(bottomActionBar, 250);
|
||||
ViewUtil.fadeOut(fab, 250);
|
||||
ViewUtil.fadeOut(cameraFab, 250);
|
||||
}
|
||||
|
||||
private void endActionModeIfActive() {
|
||||
if (actionMode != null) {
|
||||
endActionMode();
|
||||
}
|
||||
}
|
||||
|
||||
private void endActionMode() {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
ViewUtil.fadeOut(bottomActionBar, 250);
|
||||
ViewUtil.fadeIn(fab, 250);
|
||||
ViewUtil.fadeIn(cameraFab, 250);
|
||||
}
|
||||
|
||||
private void onSubmitList(@NonNull List<Conversation> conversationList) {
|
||||
defaultAdapter.submitList(conversationList);
|
||||
onPostSubmitList(conversationList.size());
|
||||
@@ -1016,10 +1021,9 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
defaultAdapter.toggleConversationInBatchSet(conversation);
|
||||
|
||||
if (defaultAdapter.getBatchSelectionIds().size() == 0) {
|
||||
actionMode.finish();
|
||||
endActionModeIfActive();
|
||||
} else {
|
||||
actionMode.setTitle(String.valueOf(defaultAdapter.getBatchSelectionIds().size()));
|
||||
setCorrectMenuVisibility(actionMode.getMenu());
|
||||
updateMultiSelectState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1035,35 +1039,35 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
Collection<Long> id = Collections.singleton(conversation.getThreadRecord().getThreadId());
|
||||
|
||||
List<SignalContextMenu.Item> items = new ArrayList<>();
|
||||
List<ActionItem> 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)));
|
||||
items.add(new ActionItem(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)));
|
||||
items.add(new ActionItem(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)));
|
||||
items.add(new ActionItem(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 ActionItem(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, () -> {
|
||||
items.add(new ActionItem(R.drawable.ic_select_24, R.string.ConversationListFragment_select, () -> {
|
||||
defaultAdapter.initializeBatchMode(true);
|
||||
defaultAdapter.toggleConversationInBatchSet(conversation);
|
||||
actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(ConversationListFragment.this);
|
||||
startActionMode();
|
||||
}));
|
||||
|
||||
if (conversation.getThreadRecord().isArchived()) {
|
||||
items.add(new SignalContextMenu.Item(R.drawable.ic_unarchive_24, R.string.ConversationListFragment_unarchive, () -> handleArchive(id, false)));
|
||||
items.add(new ActionItem(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 ActionItem(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)));
|
||||
items.add(new ActionItem(R.drawable.ic_delete_24, R.string.ConversationListFragment_delete, () -> handleDelete(id)));
|
||||
|
||||
new SignalContextMenu.Builder(view, list)
|
||||
.offsetX(ViewUtil.dpToPx(12))
|
||||
@@ -1076,45 +1080,26 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
MenuInflater inflater = getActivity().getMenuInflater();
|
||||
|
||||
inflater.inflate(R.menu.conversation_list_batch_pin, menu);
|
||||
inflater.inflate(getActionModeMenuRes(), menu);
|
||||
inflater.inflate(R.menu.conversation_list_batch, menu);
|
||||
|
||||
mode.setTitle("1");
|
||||
|
||||
WindowUtil.setStatusBarColor(requireActivity().getWindow(), getResources().getColor(R.color.action_mode_status_bar));
|
||||
|
||||
mode.setTitle(requireContext().getResources().getQuantityString(R.plurals.ConversationListFragment_s_selected, 1, 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
setCorrectMenuVisibility(menu);
|
||||
updateMultiSelectState();
|
||||
return false;
|
||||
}
|
||||
|
||||
@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: 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;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
defaultAdapter.initializeBatchMode(false);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
TypedArray color = getActivity().getTheme().obtainStyledAttributes(new int[] {android.R.attr.statusBarColor});
|
||||
WindowUtil.setStatusBarColor(getActivity().getWindow(), color.getColor(0, Color.BLACK));
|
||||
color.recycle();
|
||||
@@ -1131,7 +1116,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
lightStatusBarAttr.recycle();
|
||||
}
|
||||
|
||||
actionMode = null;
|
||||
endActionModeIfActive();
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
@@ -1145,29 +1130,41 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
closeSearchIfOpen();
|
||||
}
|
||||
|
||||
private void setCorrectMenuVisibility(@NonNull Menu menu) {
|
||||
private void updateMultiSelectState() {
|
||||
int count = defaultAdapter.getBatchSelectionIds().size();
|
||||
boolean hasUnread = Stream.of(defaultAdapter.getBatchSelection()).anyMatch(conversation -> !conversation.getThreadRecord().isRead());
|
||||
boolean hasUnpinned = Stream.of(defaultAdapter.getBatchSelection()).anyMatch(conversation -> !conversation.getThreadRecord().isPinned());
|
||||
boolean canPin = viewModel.getPinnedCount() < MAXIMUM_PINNED_CONVERSATIONS;
|
||||
|
||||
if (actionMode != null) {
|
||||
actionMode.setTitle(requireContext().getResources().getQuantityString(R.plurals.ConversationListFragment_s_selected, count, count));
|
||||
}
|
||||
|
||||
List<ActionItem> items = new ArrayList<>();
|
||||
|
||||
if (hasUnread) {
|
||||
menu.findItem(R.id.menu_mark_as_unread).setVisible(false);
|
||||
menu.findItem(R.id.menu_mark_as_read).setVisible(true);
|
||||
items.add(new ActionItem(R.drawable.ic_read_24, R.string.ConversationListFragment_read, () -> handleMarkAsRead(defaultAdapter.getBatchSelectionIds())));
|
||||
} else {
|
||||
menu.findItem(R.id.menu_mark_as_unread).setVisible(true);
|
||||
menu.findItem(R.id.menu_mark_as_read).setVisible(false);
|
||||
items.add(new ActionItem(R.drawable.ic_unread_24, R.string.ConversationListFragment_unread, () -> handleMarkAsUnread(defaultAdapter.getBatchSelectionIds())));
|
||||
}
|
||||
|
||||
if (!isArchived() && hasUnpinned && canPin) {
|
||||
menu.findItem(R.id.menu_pin_selected).setVisible(true);
|
||||
menu.findItem(R.id.menu_unpin_selected).setVisible(false);
|
||||
items.add(new ActionItem(R.drawable.ic_pin_24, R.string.ConversationListFragment_pin, () -> handlePin(defaultAdapter.getBatchSelection())));
|
||||
} else if (!isArchived() && !hasUnpinned) {
|
||||
menu.findItem(R.id.menu_pin_selected).setVisible(false);
|
||||
menu.findItem(R.id.menu_unpin_selected).setVisible(true);
|
||||
} else {
|
||||
menu.findItem(R.id.menu_pin_selected).setVisible(false);
|
||||
menu.findItem(R.id.menu_unpin_selected).setVisible(false);
|
||||
items.add(new ActionItem(R.drawable.ic_unpin_24, R.string.ConversationListFragment_unpin, () -> handleUnpin(defaultAdapter.getBatchSelectionIds())));
|
||||
}
|
||||
|
||||
if (isArchived()) {
|
||||
items.add(new ActionItem(R.drawable.ic_unarchive_24, R.string.ConversationListFragment_unarchive, () -> handleArchive(defaultAdapter.getBatchSelectionIds(), true)));
|
||||
} else {
|
||||
items.add(new ActionItem(R.drawable.ic_archive_24, R.string.ConversationListFragment_archive, () -> handleArchive(defaultAdapter.getBatchSelectionIds(), true)));
|
||||
}
|
||||
|
||||
items.add(new ActionItem(R.drawable.ic_delete_24, R.string.ConversationListFragment_delete, () -> handleDelete(defaultAdapter.getBatchSelectionIds())));
|
||||
|
||||
items.add(new ActionItem(R.drawable.ic_select_24, R.string.ConversationListFragment_select_all, this::handleSelectAllThreads));
|
||||
|
||||
bottomActionBar.setItems(items);
|
||||
}
|
||||
|
||||
protected Toolbar getToolbar(@NonNull View rootView) {
|
||||
@@ -1178,10 +1175,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
return R.plurals.ConversationListFragment_conversations_archived;
|
||||
}
|
||||
|
||||
protected @MenuRes int getActionModeMenuRes() {
|
||||
return R.menu.conversation_list_batch_archive;
|
||||
}
|
||||
|
||||
protected @DrawableRes int getArchiveIconRes() {
|
||||
return R.drawable.ic_archive_white_36dp;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user