Add new bottom actionbar to chat list.

fork-5.53.8
Greyson Parrelli 2021-10-21 10:55:33 -04:00
rodzic 2167522f7d
commit f533a898f5
23 zmienionych plików z 335 dodań i 199 usunięć

Wyświetl plik

@ -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
)

Wyświetl plik

@ -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() }
}
}

Wyświetl plik

@ -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()
}

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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="M20,12C20,12.2967 19.912,12.5867 19.7472,12.8334C19.5824,13.08 19.3481,13.2723 19.074,13.3858C18.7999,13.4994 18.4983,13.5291 18.2074,13.4712C17.9164,13.4133 17.6491,13.2704 17.4393,13.0607C17.2296,12.8509 17.0867,12.5836 17.0288,12.2926C16.9709,12.0017 17.0007,11.7001 17.1142,11.426C17.2277,11.1519 17.42,10.9176 17.6666,10.7528C17.9133,10.588 18.2033,10.5 18.5,10.5C18.8978,10.5 19.2794,10.658 19.5607,10.9393C19.842,11.2206 20,11.6022 20,12ZM12,10.5C11.7033,10.5 11.4133,10.588 11.1666,10.7528C10.92,10.9176 10.7277,11.1519 10.6142,11.426C10.5007,11.7001 10.4709,12.0017 10.5288,12.2926C10.5867,12.5836 10.7296,12.8509 10.9393,13.0607C11.1491,13.2704 11.4164,13.4133 11.7074,13.4712C11.9983,13.5291 12.2999,13.4994 12.574,13.3858C12.8481,13.2723 13.0824,13.08 13.2472,12.8334C13.412,12.5867 13.5,12.2967 13.5,12C13.5,11.6022 13.342,11.2206 13.0607,10.9393C12.7794,10.658 12.3978,10.5 12,10.5ZM5.5,10.5C5.2033,10.5 4.9133,10.588 4.6666,10.7528C4.42,10.9176 4.2277,11.1519 4.1142,11.426C4.0006,11.7001 3.9709,12.0017 4.0288,12.2926C4.0867,12.5836 4.2296,12.8509 4.4393,13.0607C4.6491,13.2704 4.9164,13.4133 5.2074,13.4712C5.4983,13.5291 5.7999,13.4994 6.074,13.3858C6.3481,13.2723 6.5824,13.08 6.7472,12.8334C6.912,12.5867 7,12.2967 7,12C7,11.6022 6.842,11.2206 6.5607,10.9393C6.2794,10.658 5.8978,10.5 5.5,10.5V10.5Z"
android:fillColor="@color/signal_icon_tint_action"/>
</vector>

Wyświetl plik

@ -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_action"
android:pathData="M19.53,5.53l-1.06,-1.06l-6.47,6.469l-6.47,-6.469l-1.06,1.06l6.469,6.47l-6.469,6.47l1.06,1.06l6.47,-6.469l6.47,6.469l1.06,-1.06l-6.469,-6.47l6.469,-6.47z"/>
</vector>

Wyświetl plik

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/signal_context_menu_corner_radius" />
<solid android:color="@color/signal_background_dialog" />
</shape>

Wyświetl plik

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/action_mode_close_button"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="24dp"
app:srcCompat="@drawable/ic_x_24" >
</ImageView>

Wyświetl plik

@ -253,4 +253,17 @@
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<org.thoughtcrime.securesms.components.menu.SignalBottomActionBar
android:id="@+id/conversation_list_bottom_action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="36dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -46,7 +46,7 @@
android:layout_width="@dimen/conversation_list_avatar_size"
android:layout_height="@dimen/conversation_list_avatar_size"
android:layout_marginTop="12dp"
android:layout_marginStart="20dp"
android:layout_marginStart="24dp"
android:contentDescription="@string/conversation_list_item_view__contact_photo_image"
android:foreground="@drawable/contact_photo_background"
app:layout_constraintStart_toEndOf="@id/conversation_list_item_check_container"

Wyświetl plik

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:gravity="center"
android:background="?selectableItemBackgroundBorderless">
<ImageView
android:id="@+id/signal_bottom_action_bar_item_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
tools:src="@drawable/ic_archive_24"/>
<TextView
android:id="@+id/signal_bottom_action_bar_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Signal.Body2"
tools:text="Archive"/>
</LinearLayout>

Wyświetl plik

@ -1,28 +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:id="@+id/menu_delete_selected"
android:icon="@drawable/ic_trash_24"
android:title="@string/conversation_list_batch__menu_delete_selected"
app:iconTint="@color/signal_icon_tint_primary"
app:showAsAction="always" />
<item
android:id="@+id/menu_mark_as_read"
android:title="@string/conversation_list_batch__menu_mark_as_read"
app:showAsAction="never" />
<item
android:id="@+id/menu_mark_as_unread"
android:title="@string/conversation_list_batch__menu_mark_as_unread"
app:showAsAction="never" />
<item
android:id="@+id/menu_select_all"
android:title="@string/conversation_list_batch__menu_select_all"
app:showAsAction="never" />
</menu>

Wyświetl plik

@ -1,11 +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_list_batch_archive__menu_archive_selected"
android:id="@+id/menu_archive_selected"
android:icon="@drawable/ic_archive_white_24dp"
app:iconTint="@color/signal_icon_tint_primary"
app:showAsAction="always"/>
</menu>

Wyświetl plik

@ -1,16 +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:id="@+id/menu_pin_selected"
android:icon="@drawable/ic_pin_24"
android:title="@string/conversation_list_batch__menu_pin_selected"
app:iconTint="@color/signal_icon_tint_primary"
app:showAsAction="always" />
<item
android:id="@+id/menu_unpin_selected"
android:icon="@drawable/ic_unpin_24"
android:title="@string/conversation_list_batch__menu_unpin_selected"
app:iconTint="@color/signal_icon_tint_primary"
app:showAsAction="always" />
</menu>

Wyświetl plik

@ -1,11 +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_list_batch_unarchive__menu_unarchive_selected"
android:id="@+id/menu_archive_selected"
android:icon="@drawable/ic_unarchive_white_24dp"
app:iconTint="@color/signal_icon_tint_primary"
app:showAsAction="always"/>
</menu>

Wyświetl plik

@ -28,6 +28,7 @@
<color name="signal_icon_tint_primary">@color/core_grey_15</color>
<color name="signal_icon_tint_secondary">@color/core_grey_25</color>
<color name="signal_icon_tint_action">@color/signal_icon_tint_primary</color>
<color name="signal_icon_tint_tab_selected">@color/core_white</color>
<color name="signal_icon_tint_tab_unselected">@color/core_grey_25</color>

Wyświetl plik

@ -28,6 +28,7 @@
<color name="signal_icon_tint_primary">@color/core_grey_75</color>
<color name="signal_icon_tint_secondary">@color/core_grey_60</color>
<color name="signal_icon_tint_action">@color/core_black</color>
<color name="signal_icon_tint_tab_selected">@color/core_grey_75</color>
<color name="signal_icon_tint_tab_unselected">@color/core_grey_45</color>

Wyświetl plik

@ -374,6 +374,11 @@
<string name="ConversationListFragment_archive">Archive</string>
<string name="ConversationListFragment_unarchive">Unarchive</string>
<string name="ConversationListFragment_delete">Delete</string>
<string name="ConversationListFragment_select_all">Select all</string>
<plurals name="ConversationListFragment_s_selected">
<item quantity="one">%d selected</item>
<item quantity="other">%d selected</item>
</plurals>
<!-- ConversationListItem -->
<string name="ConversationListItem_key_exchange_message">Key exchange message</string>
@ -1580,6 +1585,9 @@
<string name="SharedContactView_invite_to_signal">Invite to Signal</string>
<string name="SharedContactView_message">Signal Message</string>
<!-- SignalBottomActionBar -->
<string name="SignalBottomActionBar_more">More</string>
<!-- SignalPinReminders -->
<string name="SignalPinReminders_well_remind_you_again_later">We\'ll remind you again later.</string>
<string name="SignalPinReminders_well_remind_you_again_tomorrow">We\'ll remind you again tomorrow.</string>

Wyświetl plik

@ -384,8 +384,10 @@
</style>
<style name="TextSecure.ActionModeStyle" parent="@style/Widget.AppCompat.ActionMode">
<item name="titleTextStyle">@style/Signal.DayNight.TitleTextStyle</item>
<item name="titleTextStyle">@style/TextAppearance.Signal.Title.ActionMode</item>
<item name="closeItemLayout">@layout/action_mode_close_layout</item>
<item name="theme">@style/Signal.ActionModeTheme</item>
<item name="android:paddingStart">10dp</item>
</style>
<style name="Signal.ActionModeTheme" parent="ThemeOverlay.AppCompat">

Wyświetl plik

@ -166,6 +166,12 @@
<style name="TextAppearance.Signal.Subtitle2" parent="@style/TextAppearance.MaterialComponents.Subtitle2">
</style>
<style name="TextAppearance.Signal.Title.ActionMode" parent="">
<item name="android:textSize">20sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:letterSpacing" tools:ignore="NewApi">0.01</item>
</style>
<style name="Signal.Text.MessageRequest.Title" parent="Base.TextAppearance.AppCompat.Title">
<item name="android:textSize">20sp</item>
<item name="android:textColor">@color/signal_text_primary</item>

Wyświetl plik

@ -148,7 +148,7 @@
<item name="actionBarStyle">@style/TextSecure.LightActionBar</item>
<item name="actionBarTabBarStyle">@style/TextSecure.LightActionBar.TabBar</item>
<item name="actionModeBackground">@color/signal_background_secondary</item>
<item name="actionModeBackground">@color/signal_background_primary</item>
<item name="actionModeCloseDrawable">@drawable/ic_x_tinted</item>
<item name="actionModeStyle">@style/TextSecure.ActionModeStyle</item>
<item name="android:textColor">@color/text_color_light_theme</item>
@ -204,7 +204,7 @@
<item name="actionBarStyle">@style/TextSecure.DarkActionBar</item>
<item name="actionBarTabBarStyle">@style/TextSecure.DarkActionBar.TabBar</item>
<item name="actionBarPopupTheme">@style/ThemeOverlay.AppCompat.Dark</item>
<item name="actionModeBackground">@color/signal_background_secondary</item>
<item name="actionModeBackground">@color/signal_background_primary</item>
<item name="actionModeCloseDrawable">@drawable/ic_x_tinted</item>
<item name="actionModeStyle">@style/TextSecure.ActionModeStyle</item>
<item name="android:textColor">@color/text_color_dark_theme</item>