mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 02:10:44 +01:00
Fix action mode to use our own action mode toolbar instead of the built in androidx one.
This commit is contained in:
committed by
Cody Henthorne
parent
4db60ac63f
commit
74d9195d94
@@ -16,6 +16,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
@@ -250,6 +251,22 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
}
|
||||
}
|
||||
|
||||
val callback = object : OnBackPressedCallback(toolbarViewModel.state.value.mode == MainToolbarMode.ACTION_MODE) {
|
||||
override fun handleOnBackPressed() {
|
||||
toolbarCallback.onCloseActionModeClick()
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
toolbarViewModel.state.collect { state ->
|
||||
callback.isEnabled = state.mode == MainToolbarMode.ACTION_MODE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, callback)
|
||||
|
||||
shareDataTimestampViewModel.setTimestampFromActivityCreation(savedInstanceState, intent)
|
||||
|
||||
setContent {
|
||||
@@ -810,6 +827,15 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
|
||||
toolbarViewModel.emitEvent(MainToolbarViewModel.Event.Chats.CloseArchive)
|
||||
}
|
||||
|
||||
override fun onCloseActionModeClick() {
|
||||
supportFragmentManager.fragments.forEach { fragment ->
|
||||
when (fragment) {
|
||||
is ConversationListFragment -> fragment.endActionModeIfActive()
|
||||
is CallLogFragment -> fragment.CallLogActionModeCallback().onActionModeWillEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSearchQueryUpdated(query: String) {
|
||||
toolbarViewModel.setSearchQuery(query)
|
||||
}
|
||||
|
||||
@@ -1,72 +1,37 @@
|
||||
package org.thoughtcrime.securesms.calls.log
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.main.MainToolbarViewModel
|
||||
|
||||
class CallLogActionMode(
|
||||
private val callback: Callback
|
||||
) : ActionMode.Callback {
|
||||
private val callback: Callback,
|
||||
private val mainToolbarViewModel: MainToolbarViewModel
|
||||
) {
|
||||
|
||||
private var actionMode: ActionMode? = null
|
||||
private var count: Int = 0
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
mode?.title = getTitle(1)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||
callback.onResetSelectionState()
|
||||
endIfActive()
|
||||
}
|
||||
|
||||
fun isInActionMode(): Boolean {
|
||||
return actionMode != null
|
||||
}
|
||||
|
||||
fun getCount(): Int {
|
||||
return if (actionMode != null) count else 0
|
||||
return if (mainToolbarViewModel.isInActionMode()) count else 0
|
||||
}
|
||||
|
||||
fun setCount(count: Int) {
|
||||
this.count = count
|
||||
actionMode?.title = getTitle(count)
|
||||
mainToolbarViewModel.setActionModeCount(count)
|
||||
}
|
||||
|
||||
fun start() {
|
||||
actionMode = callback.startActionMode(this)
|
||||
callback.startActionMode()
|
||||
}
|
||||
|
||||
fun end() {
|
||||
callback.onActionModeWillEnd()
|
||||
actionMode?.finish()
|
||||
count = 0
|
||||
actionMode = null
|
||||
}
|
||||
|
||||
private fun getTitle(callLogsSelected: Int): String {
|
||||
return callback.getResources().getQuantityString(R.plurals.ConversationListFragment_s_selected, callLogsSelected, callLogsSelected)
|
||||
}
|
||||
|
||||
private fun endIfActive() {
|
||||
if (actionMode != null) {
|
||||
end()
|
||||
if (mainToolbarViewModel.isInActionMode()) {
|
||||
callback.onActionModeWillEnd()
|
||||
count = 0
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun startActionMode(callback: ActionMode.Callback): ActionMode?
|
||||
fun startActionMode()
|
||||
fun onActionModeWillEnd()
|
||||
fun getResources(): Resources
|
||||
fun onResetSelectionState()
|
||||
|
||||
@@ -7,8 +7,6 @@ import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
@@ -77,7 +75,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
|
||||
private val disposables = LifecycleDisposable()
|
||||
private val callLogContextMenu = CallLogContextMenu(this, this)
|
||||
private val callLogActionMode = CallLogActionMode(CallLogActionModeCallback())
|
||||
private lateinit var callLogActionMode: CallLogActionMode
|
||||
private val conversationUpdateTick: ConversationUpdateTick = ConversationUpdateTick(this::onTimestampTick)
|
||||
private var callLogAdapter: CallLogAdapter? = null
|
||||
|
||||
@@ -91,6 +89,8 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
viewLifecycleOwner.lifecycle.addObserver(conversationUpdateTick)
|
||||
viewLifecycleOwner.lifecycle.addObserver(viewModel.callLogPeekHelper)
|
||||
|
||||
callLogActionMode = CallLogActionMode(CallLogActionModeCallback(), mainToolbarViewModel)
|
||||
|
||||
val callLogAdapter = CallLogAdapter(this)
|
||||
disposables.bindTo(viewLifecycleOwner)
|
||||
|
||||
@@ -134,7 +134,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
.subscribe { (selected, totalCount) ->
|
||||
if (selected.isNotEmpty(totalCount)) {
|
||||
callLogActionMode.setCount(selected.count(totalCount))
|
||||
} else if (callLogActionMode.isInActionMode()) {
|
||||
} else if (mainToolbarViewModel.isInActionMode()) {
|
||||
callLogActionMode.end()
|
||||
}
|
||||
}
|
||||
@@ -275,7 +275,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
}
|
||||
|
||||
override fun canStartNestedScroll(): Boolean {
|
||||
return !callLogActionMode.isInActionMode() && !isSearchOpen()
|
||||
return !mainToolbarViewModel.isInActionMode() && !isSearchOpen()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,17 +464,16 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
||||
override fun onBottomActionBarVisibilityChanged(visibility: Int) = Unit
|
||||
}
|
||||
|
||||
private inner class CallLogActionModeCallback : CallLogActionMode.Callback {
|
||||
override fun startActionMode(callback: ActionMode.Callback): ActionMode? {
|
||||
val actionMode = (requireActivity() as AppCompatActivity).startSupportActionMode(callback)
|
||||
inner class CallLogActionModeCallback : CallLogActionMode.Callback {
|
||||
override fun startActionMode() {
|
||||
requireListener<Callback>().onMultiSelectStarted()
|
||||
signalBottomActionBarController.setVisibility(true)
|
||||
return actionMode
|
||||
}
|
||||
|
||||
override fun onActionModeWillEnd() {
|
||||
requireListener<Callback>().onMultiSelectFinished()
|
||||
signalBottomActionBarController.setVisibility(false)
|
||||
viewModel.clearSelected()
|
||||
}
|
||||
|
||||
override fun getResources(): Resources = resources
|
||||
|
||||
@@ -26,9 +26,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.compose.material3.SnackbarDuration;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable;
|
||||
@@ -48,7 +46,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import kotlin.Unit;
|
||||
|
||||
|
||||
public class ConversationListArchiveFragment extends ConversationListFragment implements ActionMode.Callback
|
||||
public class ConversationListArchiveFragment extends ConversationListFragment
|
||||
{
|
||||
private View coordinator;
|
||||
private RecyclerView list;
|
||||
|
||||
@@ -30,8 +30,6 @@ import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
@@ -44,9 +42,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.PluralsRes;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.compose.material3.SnackbarDuration;
|
||||
import androidx.compose.ui.platform.ComposeView;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
@@ -184,8 +180,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import kotlin.Unit;
|
||||
|
||||
|
||||
public class ConversationListFragment extends MainFragment implements ActionMode.Callback,
|
||||
ConversationListAdapter.OnConversationClickListener,
|
||||
public class ConversationListFragment extends MainFragment implements ConversationListAdapter.OnConversationClickListener,
|
||||
ClearFilterViewHolder.OnClearFilterClickListener,
|
||||
ChatFolderAdapter.Callbacks,
|
||||
ConversationListAdapter.EmptyFolderViewHolder.OnFolderSettingsClickListener
|
||||
@@ -201,8 +196,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
private static final int MAX_CHATS_ABOVE_FOLD = 7;
|
||||
private static final int MAX_CONTACTS_ABOVE_FOLD = 5;
|
||||
private static final int MAX_GROUP_MEMBERSHIPS_ABOVE_FOLD = 5;
|
||||
|
||||
private ActionMode actionMode;
|
||||
private View coordinator;
|
||||
private RecyclerView chatFolderList;
|
||||
private RecyclerView list;
|
||||
@@ -1150,22 +1143,20 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
}
|
||||
|
||||
private void startActionMode() {
|
||||
actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(ConversationListFragment.this);
|
||||
ViewUtil.animateIn(bottomActionBar, bottomActionBar.getEnterAnimation());
|
||||
requireCallback().onMultiSelectStarted();
|
||||
}
|
||||
|
||||
private void endActionModeIfActive() {
|
||||
if (actionMode != null) {
|
||||
public void endActionModeIfActive() {
|
||||
if (mainToolbarViewModel.isInActionMode()) {
|
||||
endActionMode();
|
||||
}
|
||||
}
|
||||
|
||||
private void endActionMode() {
|
||||
actionMode.finish();
|
||||
actionMode = null;
|
||||
ViewUtil.animateOut(bottomActionBar, bottomActionBar.getExitAnimation());
|
||||
requireCallback().onMultiSelectFinished();
|
||||
viewModel.endSelection();
|
||||
}
|
||||
|
||||
void updateEmptyState(boolean isConversationEmpty) {
|
||||
@@ -1186,7 +1177,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
@Override
|
||||
public void onConversationClick(@NonNull Conversation conversation) {
|
||||
if (actionMode == null) {
|
||||
if (!mainToolbarViewModel.isInActionMode()) {
|
||||
handleCreateConversation(conversation.getThreadRecord().getThreadId(), conversation.getThreadRecord().getRecipient(), conversation.getThreadRecord().getDistributionType());
|
||||
} else {
|
||||
viewModel.toggleConversationSelected(conversation);
|
||||
@@ -1195,7 +1186,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
@Override
|
||||
public boolean onConversationLongClick(@NonNull Conversation conversation, @NonNull View view) {
|
||||
if (actionMode != null) {
|
||||
if (mainToolbarViewModel.isInActionMode()) {
|
||||
onConversationClick(conversation);
|
||||
return true;
|
||||
}
|
||||
@@ -1269,29 +1260,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
mode.setTitle(requireContext().getResources().getQuantityString(R.plurals.ConversationListFragment_s_selected, 1, 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
|
||||
updateMultiSelectState();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
viewModel.endSelection();
|
||||
endActionModeIfActive();
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
|
||||
public void onEvent(MessageSender.MessageSentEvent event) {
|
||||
EventBus.getDefault().removeStickyEvent(event);
|
||||
@@ -1351,8 +1319,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
boolean hasUnmuted = Stream.of(viewModel.currentSelectedConversations()).anyMatch(conversation -> !conversation.getThreadRecord().getRecipient().live().get().isMuted());
|
||||
boolean canPin = viewModel.getPinnedCount() < MAXIMUM_PINNED_CONVERSATIONS;
|
||||
|
||||
if (actionMode != null) {
|
||||
actionMode.setTitle(requireContext().getResources().getQuantityString(R.plurals.ConversationListFragment_s_selected, count, count));
|
||||
if (mainToolbarViewModel.isInActionMode()) {
|
||||
mainToolbarViewModel.setActionModeCount(count);
|
||||
}
|
||||
|
||||
List<ActionItem> items = new ArrayList<>();
|
||||
@@ -1622,7 +1590,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
viewHolder instanceof ConversationListAdapter.HeaderViewHolder ||
|
||||
viewHolder instanceof ClearFilterViewHolder ||
|
||||
viewHolder instanceof ConversationListAdapter.EmptyFolderViewHolder ||
|
||||
actionMode != null ||
|
||||
mainToolbarViewModel.isInActionMode() ||
|
||||
viewHolder.itemView.isSelected() ||
|
||||
activeAdapter == searchAdapter)
|
||||
{
|
||||
|
||||
@@ -78,6 +78,7 @@ import org.thoughtcrime.securesms.avatar.AvatarImage
|
||||
import org.thoughtcrime.securesms.calls.log.CallLogFilter
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.BadgeImageSmall
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
|
||||
import org.thoughtcrime.securesms.dependencies.GooglePlayBillingDependencies.context
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.rememberRecipientField
|
||||
|
||||
@@ -98,6 +99,7 @@ interface MainToolbarCallback {
|
||||
fun onStoryPrivacyClick()
|
||||
fun onCloseSearchClick()
|
||||
fun onCloseArchiveClick()
|
||||
fun onCloseActionModeClick()
|
||||
fun onSearchQueryUpdated(query: String)
|
||||
fun onNotificationProfileTooltipDismissed()
|
||||
|
||||
@@ -118,6 +120,7 @@ interface MainToolbarCallback {
|
||||
override fun onStoryPrivacyClick() = Unit
|
||||
override fun onCloseSearchClick() = Unit
|
||||
override fun onCloseArchiveClick() = Unit
|
||||
override fun onCloseActionModeClick() = Unit
|
||||
override fun onSearchQueryUpdated(query: String) = Unit
|
||||
override fun onNotificationProfileTooltipDismissed() = Unit
|
||||
}
|
||||
@@ -144,7 +147,8 @@ data class MainToolbarState(
|
||||
val hasPassphrase: Boolean = false,
|
||||
val proxyState: ProxyState = ProxyState.NONE,
|
||||
@StringRes val searchHint: Int = R.string.SearchToolbar_search,
|
||||
val searchQuery: String = ""
|
||||
val searchQuery: String = "",
|
||||
val actionModeCount: Int = 0
|
||||
) {
|
||||
enum class ProxyState(@DrawableRes val icon: Int) {
|
||||
NONE(-1),
|
||||
@@ -161,7 +165,10 @@ fun MainToolbar(
|
||||
callback: MainToolbarCallback
|
||||
) {
|
||||
if (state.mode == MainToolbarMode.ACTION_MODE) {
|
||||
TopAppBar(title = {})
|
||||
ActionModeToolbar(
|
||||
state = state,
|
||||
callback = callback
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -209,6 +216,32 @@ fun MainToolbar(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ActionModeToolbar(
|
||||
state: MainToolbarState,
|
||||
callback: MainToolbarCallback
|
||||
) {
|
||||
TopAppBar(
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = state.toolbarColor ?: MaterialTheme.colorScheme.surface
|
||||
),
|
||||
navigationIcon = {
|
||||
IconButtons.IconButton(onClick = {
|
||||
callback.onCloseActionModeClick()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.symbol_x_24),
|
||||
contentDescription = stringResource(R.string.CallScreenTopBar__go_back)
|
||||
)
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = context.resources.getQuantityString(R.plurals.ConversationListFragment_s_selected, state.actionModeCount, state.actionModeCount))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchToolbar(
|
||||
state: MainToolbarState,
|
||||
|
||||
@@ -73,6 +73,14 @@ class MainToolbarViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun setActionModeCount(count: Int) {
|
||||
internalStateFlow.update {
|
||||
it.copy(actionModeCount = count)
|
||||
}
|
||||
}
|
||||
|
||||
fun isInActionMode(): Boolean = state.value.mode == MainToolbarMode.ACTION_MODE
|
||||
|
||||
fun presentToolbarForConversationListFragment() {
|
||||
setToolbarMode(MainToolbarMode.FULL, destination = MainNavigationListLocation.CHATS, overwriteSearchMode = false)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user