diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/snackbars/SnackbarState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/snackbars/SnackbarState.kt index 9d66057006..1bd012498a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/snackbars/SnackbarState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/snackbars/SnackbarState.kt @@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.R * * @property message The text message to display in the snackbar. * @property actionState Optional action button configuration. - * @property showProgress Whether to show a progress indicator in the snackbar. * @property duration How long the snackbar should be displayed. * @property hostKey The target host where this snackbar should be displayed. Defaults to [SnackbarHostKey.Global] * @property fallbackKey Optional host to fallback upon if the host key is not registered. Defaults to the Global key. @@ -22,7 +21,6 @@ import org.thoughtcrime.securesms.R data class SnackbarState( val message: String, val actionState: ActionState? = null, - val showProgress: Boolean = false, val duration: Snackbars.Duration = Snackbars.Duration.SHORT, val hostKey: SnackbarHostKey = SnackbarHostKey.Global, val fallbackKey: SnackbarHostKey? = SnackbarHostKey.Global diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/snackbars/SnackbarStateConsumerRegistry.kt b/app/src/main/java/org/thoughtcrime/securesms/components/snackbars/SnackbarStateConsumerRegistry.kt index 11df77b495..c948ca6dd4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/snackbars/SnackbarStateConsumerRegistry.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/snackbars/SnackbarStateConsumerRegistry.kt @@ -8,7 +8,7 @@ package org.thoughtcrime.securesms.components.snackbars import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.MutableState -import androidx.compose.runtime.State +import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf @@ -27,11 +27,30 @@ val LocalSnackbarStateConsumerRegistry = staticCompositionLocalOf +) { + val value: SnackbarState? + get() = state.value + + /** + * Clears the current snackbar state. Should be called after the snackbar has been consumed/dismissed. + */ + fun clear() { + state.value = null + } +} + @Composable fun rememberSnackbarState( key: SnackbarHostKey -): State { +): SnackbarStateHolder { val state: MutableState = remember(key) { mutableStateOf(null) } + val holder = remember(key, state) { SnackbarStateHolder(state) } val registry = LocalSnackbarStateConsumerRegistry.current DisposableEffect(registry, key) { @@ -44,7 +63,7 @@ fun rememberSnackbarState( } } - return state + return holder } /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListArchiveFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListArchiveFragment.java index f329ea8cab..12f0814c26 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListArchiveFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListArchiveFragment.java @@ -151,7 +151,6 @@ public class ConversationListArchiveFragment extends ConversationListFragment return Unit.INSTANCE; } ), - false, Snackbars.Duration.LONG, MainSnackbarHostKey.MainChrome.INSTANCE, null diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 7104c9fb88..3271b05ed6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -68,6 +68,7 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import org.signal.core.util.DimensionUnit; import org.signal.core.util.Stopwatch; +import org.signal.core.util.ThreadUtil; import org.signal.core.util.concurrent.LifecycleDisposable; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.concurrent.SimpleTask; @@ -158,7 +159,7 @@ import org.thoughtcrime.securesms.util.SignalProxyUtil; import org.thoughtcrime.securesms.util.SnapToTopDataObserver; import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.adapter.mapping.PagingMappingAdapter; -import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; +import org.thoughtcrime.securesms.components.SignalProgressDialog; import org.thoughtcrime.securesms.util.views.Stub; import org.thoughtcrime.securesms.wallpaper.ChatWallpaper; import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState; @@ -230,6 +231,7 @@ public class ConversationListFragment extends MainFragment implements Conversati private ChatListBackHandler chatListBackHandler; private BannerManager bannerManager; + private SignalProgressDialog progressDialog; protected MainNavigationViewModel mainNavigationViewModel; @@ -464,6 +466,8 @@ public class ConversationListFragment extends MainFragment implements Conversati defaultAdapter = null; searchAdapter = null; + dismissProgressDialog(); + super.onDestroyView(); } @@ -968,16 +972,23 @@ public class ConversationListFragment extends MainFragment implements Conversati } @SuppressLint("StaticFieldLeak") - private void handleArchive(@NonNull Collection ids, boolean showProgress) { + private void handleArchive(@NonNull Collection ids) { Set selectedConversations = new HashSet<>(ids); int count = selectedConversations.size(); String snackBarTitle = getResources().getQuantityString(getArchivedSnackbarTitleRes(), count, count); + boolean showProgress = count > 1; + + dismissProgressDialog(); + if (showProgress) { + progressDialog = SignalProgressDialog.show(requireContext(), null, null, true, false, null); + } lifecycleDisposable.add(Completable .fromAction(() -> archiveThreads(selectedConversations)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(() -> { + dismissProgressDialog(); endActionModeIfActive(); mainNavigationViewModel.getSnackbarRegistry().emit(new SnackbarState( @@ -986,11 +997,10 @@ public class ConversationListFragment extends MainFragment implements Conversati getString(R.string.ConversationListFragment_undo), R.color.amber_500, () -> { - SignalExecutors.BOUNDED_IO.execute(() -> reverseArchiveThreads(selectedConversations)); + handleUnarchive(selectedConversations); return Unit.INSTANCE; } ), - showProgress, Snackbars.Duration.LONG, MainSnackbarHostKey.MainChrome.INSTANCE, null @@ -998,6 +1008,27 @@ public class ConversationListFragment extends MainFragment implements Conversati })); } + private void handleUnarchive(@NonNull Set threadIds) { + boolean showProgress = threadIds.size() > 1; + + dismissProgressDialog(); + if (showProgress) { + progressDialog = SignalProgressDialog.show(requireContext(), null, null, true, false, null); + } + + SignalExecutors.BOUNDED_IO.execute(() -> { + reverseArchiveThreads(threadIds); + ThreadUtil.runOnMain(this::dismissProgressDialog); + }); + } + + private void dismissProgressDialog() { + if (progressDialog != null) { + progressDialog.dismiss(); + progressDialog = null; + } + } + @SuppressLint("StaticFieldLeak") private void handleDelete(@NonNull Collection ids) { if (DeleteSyncEducationDialog.shouldShow()) { @@ -1074,7 +1105,6 @@ public class ConversationListFragment extends MainFragment implements Conversati mainNavigationViewModel.getSnackbarRegistry().emit(new SnackbarState( getString(R.string.conversation_list__you_can_only_pin_up_to_d_chats, MAXIMUM_PINNED_CONVERSATIONS), null, - false, Snackbars.Duration.LONG, MainSnackbarHostKey.MainChrome.INSTANCE, null @@ -1120,7 +1150,8 @@ public class ConversationListFragment extends MainFragment implements Conversati } private void updateMute(@NonNull Collection conversations, long until) { - SimpleProgressDialog.DismissibleDialog dialog = SimpleProgressDialog.showDelayed(requireContext(), 250, 250); + dismissProgressDialog(); + progressDialog = SignalProgressDialog.show(requireContext(), null, null, true, false, null); SimpleTask.run(SignalExecutors.BOUNDED, () -> { List recipientIds = conversations.stream() @@ -1132,7 +1163,7 @@ public class ConversationListFragment extends MainFragment implements Conversati return null; }, unused -> { endActionModeIfActive(); - dialog.dismiss(); + dismissProgressDialog(); }); } @@ -1245,7 +1276,7 @@ public class ConversationListFragment extends MainFragment implements Conversati })); if (conversation.getThreadRecord().isArchived()) { - items.add(new ActionItem(R.drawable.symbol_archive_up_24, getResources().getString(R.string.ConversationListFragment_unarchive), () -> handleArchive(id, false))); + items.add(new ActionItem(R.drawable.symbol_archive_up_24, getResources().getString(R.string.ConversationListFragment_unarchive), () -> handleArchive(id))); } else { if (viewModel.getCurrentFolder().getFolderType() == ChatFolderRecord.FolderType.ALL && (conversation.getThreadRecord().getRecipient().isIndividual() || @@ -1257,7 +1288,7 @@ public class ConversationListFragment extends MainFragment implements Conversati } else if (viewModel.getCurrentFolder().getFolderType() != ChatFolderRecord.FolderType.ALL) { items.add(new ActionItem(R.drawable.symbol_folder_minus, getString(R.string.ConversationListFragment_remove_from_folder), () -> viewModel.removeChatFromFolder(conversation.getThreadRecord().getThreadId()))); } - items.add(new ActionItem(R.drawable.symbol_archive_24, getResources().getString(R.string.ConversationListFragment_archive), () -> handleArchive(id, false))); + items.add(new ActionItem(R.drawable.symbol_archive_24, getResources().getString(R.string.ConversationListFragment_archive), () -> handleArchive(id))); } items.add(new ActionItem(R.drawable.symbol_trash_24, getResources().getString(R.string.ConversationListFragment_delete), () -> handleDelete(id))); @@ -1360,9 +1391,9 @@ public class ConversationListFragment extends MainFragment implements Conversati } if (isArchived()) { - items.add(new ActionItem(R.drawable.symbol_archive_up_24, getResources().getString(R.string.ConversationListFragment_unarchive), () -> handleArchive(selectionIds, true))); + items.add(new ActionItem(R.drawable.symbol_archive_up_24, getResources().getString(R.string.ConversationListFragment_unarchive), () -> handleArchive(selectionIds))); } else { - items.add(new ActionItem(R.drawable.symbol_archive_24, getResources().getString(R.string.ConversationListFragment_archive), () -> handleArchive(selectionIds, true))); + items.add(new ActionItem(R.drawable.symbol_archive_24, getResources().getString(R.string.ConversationListFragment_archive), () -> handleArchive(selectionIds))); } items.add(new ActionItem(R.drawable.symbol_trash_24, getResources().getString(R.string.ConversationListFragment_delete), () -> handleDelete(selectionIds))); @@ -1440,7 +1471,6 @@ public class ConversationListFragment extends MainFragment implements Conversati return Unit.INSTANCE; } ), - false, Snackbars.Duration.LONG, MainSnackbarHostKey.MainChrome.INSTANCE, null diff --git a/app/src/main/java/org/thoughtcrime/securesms/main/MainBottomChrome.kt b/app/src/main/java/org/thoughtcrime/securesms/main/MainBottomChrome.kt index d1c7d81025..bc891fa792 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/main/MainBottomChrome.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/main/MainBottomChrome.kt @@ -21,12 +21,10 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import org.signal.core.ui.compose.AllDevicePreviews -import org.signal.core.ui.compose.Dialogs import org.signal.core.ui.compose.Previews import org.signal.core.ui.compose.Snackbars import org.signal.core.ui.compose.showSnackbar import org.thoughtcrime.securesms.components.snackbars.SnackbarHostKey -import org.thoughtcrime.securesms.components.snackbars.SnackbarState import org.thoughtcrime.securesms.components.snackbars.rememberSnackbarState import org.thoughtcrime.securesms.megaphone.Megaphone import org.thoughtcrime.securesms.megaphone.MegaphoneActionController @@ -118,18 +116,14 @@ fun MainSnackbar( hostKey: SnackbarHostKey = MainSnackbarHostKey.MainChrome ) { val hostState = remember { SnackbarHostState() } - val state: SnackbarState? by rememberSnackbarState(hostKey) - val snackbarState = state + val stateHolder = rememberSnackbarState(hostKey) + val snackbarState = stateHolder.value Snackbars.Host( hostState, modifier = modifier ) - if (snackbarState?.showProgress == true) { - Dialogs.IndeterminateProgressDialog() - } - LaunchedEffect(snackbarState) { if (snackbarState != null) { val result = hostState.showSnackbar( @@ -143,6 +137,7 @@ fun MainSnackbar( SnackbarResult.ActionPerformed -> snackbarState.actionState?.onActionClick?.invoke() } + stateHolder.clear() onDismissed() } }