diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt index f3ca8b9234..2759e49f70 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt @@ -45,7 +45,7 @@ class LoggedInEventProcessor @Inject constructor( observingJob = null } - private suspend fun displayMessage(message: Int) { + private fun displayMessage(message: Int) { snackbarDispatcher.post(SnackbarMessage(message)) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index da50881d14..a01a3ed3c8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -484,7 +484,7 @@ class MessagesPresenter @AssistedInject constructor( ) } - private suspend fun handleCopyContents(event: TimelineItem.Event) { + private fun handleCopyContents(event: TimelineItem.Event) { val content = when (event.content) { is TimelineItemTextBasedContent -> event.content.body is TimelineItemStateContent -> event.content.body @@ -496,7 +496,7 @@ class MessagesPresenter @AssistedInject constructor( } } - private suspend fun handleCopyCaption(event: TimelineItem.Event) { + private fun handleCopyCaption(event: TimelineItem.Event) { val content = (event.content as? TimelineItemEventContentWithAttachment)?.caption ?: return clipboardHelper.copyPlainText(content) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupNode.kt index 1fcd1b792f..03f4424158 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupNode.kt @@ -8,7 +8,6 @@ package io.element.android.features.securebackup.impl.setup import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node @@ -22,8 +21,6 @@ import io.element.android.libraries.architecture.inputs import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.di.SessionScope -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch @ContributesNode(SessionScope::class) class SecureBackupSetupNode @AssistedInject constructor( @@ -42,12 +39,11 @@ class SecureBackupSetupNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { - val coroutineScope = rememberCoroutineScope() val state = presenter.present() SecureBackupSetupView( state = state, onSuccess = { - coroutineScope.postSuccessSnackbar() + postSuccessSnackbar() navigateUp() }, onBackClick = ::navigateUp, @@ -55,7 +51,7 @@ class SecureBackupSetupNode @AssistedInject constructor( ) } - private fun CoroutineScope.postSuccessSnackbar() = launch { + private fun postSuccessSnackbar() { snackbarDispatcher.post( SnackbarMessage( messageResId = if (inputs.isChangeRecoveryKeyUserStory) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarDispatcher.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarDispatcher.kt index 336d706d2e..52fe5979e6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarDispatcher.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarDispatcher.kt @@ -36,7 +36,7 @@ class SnackbarDispatcher { } } - suspend fun post(message: SnackbarMessage) { + fun post(message: SnackbarMessage) { if (snackBarMessageQueue.isEmpty()) { snackBarMessageQueue.add(message) if (queueMutex.isLocked) queueMutex.unlock() @@ -54,7 +54,7 @@ class SnackbarDispatcher { } /** Used to provide a [SnackbarDispatcher] to composable functions, it's needed for [rememberSnackbarHostState]. */ -val LocalSnackbarDispatcher = compositionLocalOf { SnackbarDispatcher() } +val LocalSnackbarDispatcher = compositionLocalOf { SnackbarDispatcher() } @Composable fun SnackbarDispatcher.collectSnackbarMessageAsState(): State { @@ -72,10 +72,10 @@ fun rememberSnackbarHostState(snackbarMessage: SnackbarMessage?): SnackbarHostSt } ?: return snackbarHostState val dispatcher = LocalSnackbarDispatcher.current - LaunchedEffect(snackbarMessageText) { + LaunchedEffect(snackbarMessage.id) { // If the message wasn't already displayed, do it now, and mark it as displayed // This will prevent the message from appearing in any other active SnackbarHosts - if (snackbarMessage.isDisplayed.getAndSet(true) == false) { + if (snackbarMessage.isDisplayed.getAndSet(true).not()) { try { snackbarHostState.showSnackbar( message = snackbarMessageText, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarMessage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarMessage.kt index 2597944c59..fb9be76f98 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarMessage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarMessage.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.designsystem.utils.snackbar import androidx.annotation.StringRes import androidx.compose.material3.SnackbarDuration import java.util.concurrent.atomic.AtomicBoolean +import kotlin.random.Random /** * A message to be displayed in a [Snackbar]. @@ -17,6 +18,7 @@ import java.util.concurrent.atomic.AtomicBoolean * @param duration The duration of the message. The default value is [SnackbarDuration.Short]. * @param actionResId The action text to be displayed. The default value is `null`. * @param isDisplayed Used to track if the current message is already displayed or not. + * @param id The unique identifier of the message. The default value is a random long. * @param action The action to be performed when the action is clicked. */ data class SnackbarMessage( @@ -24,5 +26,6 @@ data class SnackbarMessage( val duration: SnackbarDuration = SnackbarDuration.Short, @StringRes val actionResId: Int? = null, val isDisplayed: AtomicBoolean = AtomicBoolean(false), + val id: Long = Random.nextLong(), val action: () -> Unit = {}, ) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index 476e86c5c5..e094e1b9fb 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -86,11 +86,13 @@ class MediaGalleryPresenter @AssistedInject constructor( mediaGalleryDataSource.deleteItem(event.eventId) } is MediaGalleryEvents.SaveOnDisk -> coroutineScope.launch { + mediaBottomSheetState = MediaBottomSheetState.Hidden groupedMediaItems.dataOrNull().find(event.eventId)?.let { saveOnDisk(it) } } is MediaGalleryEvents.Share -> coroutineScope.launch { + mediaBottomSheetState = MediaBottomSheetState.Hidden groupedMediaItems.dataOrNull().find(event.eventId)?.let { share(it) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt index a801437e82..b039576e0a 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt @@ -44,7 +44,6 @@ class MediaViewerPresenter @AssistedInject constructor( @Assisted private val dataSource: MediaViewerDataSource, private val room: MatrixRoom, private val localMediaActions: LocalMediaActions, - private val snackbarDispatcher: SnackbarDispatcher, ) : Presenter { @AssistedFactory interface Factory { @@ -55,6 +54,9 @@ class MediaViewerPresenter @AssistedInject constructor( ): MediaViewerPresenter } + // Use a local snackbarDispatcher because this presenter is used in an Overlay Node + private val snackbarDispatcher = SnackbarDispatcher() + @Composable override fun present(): MediaViewerState { val coroutineScope = rememberCoroutineScope() diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenterTest.kt index ac0acfe719..47e48ca9e1 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenterTest.kt @@ -13,7 +13,6 @@ import android.net.Uri import app.cash.turbine.ReceiveTurbine import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -568,7 +567,6 @@ class MediaViewerPresenterTest { eventId: EventId? = null, matrixMediaLoader: FakeMatrixMediaLoader = FakeMatrixMediaLoader(), localMediaActions: FakeLocalMediaActions = FakeLocalMediaActions(), - snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(), mediaGalleryDataSource: MediaGalleryDataSource = FakeMediaGalleryDataSource( startLambda = { }, ), @@ -598,7 +596,6 @@ class MediaViewerPresenterTest { ), room = room, localMediaActions = localMediaActions, - snackbarDispatcher = snackbarDispatcher, ) } }