Merge pull request #4195 from element-hq/feature/bma/snackBar

Fix snack bar not displayed in MediaViewer
This commit is contained in:
Benoit Marty 2025-01-27 09:52:36 +01:00 committed by GitHub
commit fa617dbda8
8 changed files with 17 additions and 17 deletions

View file

@ -45,7 +45,7 @@ class LoggedInEventProcessor @Inject constructor(
observingJob = null observingJob = null
} }
private suspend fun displayMessage(message: Int) { private fun displayMessage(message: Int) {
snackbarDispatcher.post(SnackbarMessage(message)) snackbarDispatcher.post(SnackbarMessage(message))
} }
} }

View file

@ -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) { val content = when (event.content) {
is TimelineItemTextBasedContent -> event.content.body is TimelineItemTextBasedContent -> event.content.body
is TimelineItemStateContent -> 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 val content = (event.content as? TimelineItemEventContentWithAttachment)?.caption ?: return
clipboardHelper.copyPlainText(content) clipboardHelper.copyPlainText(content)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {

View file

@ -8,7 +8,6 @@
package io.element.android.features.securebackup.impl.setup package io.element.android.features.securebackup.impl.setup
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node 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.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ContributesNode(SessionScope::class) @ContributesNode(SessionScope::class)
class SecureBackupSetupNode @AssistedInject constructor( class SecureBackupSetupNode @AssistedInject constructor(
@ -42,12 +39,11 @@ class SecureBackupSetupNode @AssistedInject constructor(
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val coroutineScope = rememberCoroutineScope()
val state = presenter.present() val state = presenter.present()
SecureBackupSetupView( SecureBackupSetupView(
state = state, state = state,
onSuccess = { onSuccess = {
coroutineScope.postSuccessSnackbar() postSuccessSnackbar()
navigateUp() navigateUp()
}, },
onBackClick = ::navigateUp, onBackClick = ::navigateUp,
@ -55,7 +51,7 @@ class SecureBackupSetupNode @AssistedInject constructor(
) )
} }
private fun CoroutineScope.postSuccessSnackbar() = launch { private fun postSuccessSnackbar() {
snackbarDispatcher.post( snackbarDispatcher.post(
SnackbarMessage( SnackbarMessage(
messageResId = if (inputs.isChangeRecoveryKeyUserStory) { messageResId = if (inputs.isChangeRecoveryKeyUserStory) {

View file

@ -36,7 +36,7 @@ class SnackbarDispatcher {
} }
} }
suspend fun post(message: SnackbarMessage) { fun post(message: SnackbarMessage) {
if (snackBarMessageQueue.isEmpty()) { if (snackBarMessageQueue.isEmpty()) {
snackBarMessageQueue.add(message) snackBarMessageQueue.add(message)
if (queueMutex.isLocked) queueMutex.unlock() if (queueMutex.isLocked) queueMutex.unlock()
@ -54,7 +54,7 @@ class SnackbarDispatcher {
} }
/** Used to provide a [SnackbarDispatcher] to composable functions, it's needed for [rememberSnackbarHostState]. */ /** Used to provide a [SnackbarDispatcher] to composable functions, it's needed for [rememberSnackbarHostState]. */
val LocalSnackbarDispatcher = compositionLocalOf<SnackbarDispatcher> { SnackbarDispatcher() } val LocalSnackbarDispatcher = compositionLocalOf { SnackbarDispatcher() }
@Composable @Composable
fun SnackbarDispatcher.collectSnackbarMessageAsState(): State<SnackbarMessage?> { fun SnackbarDispatcher.collectSnackbarMessageAsState(): State<SnackbarMessage?> {
@ -72,10 +72,10 @@ fun rememberSnackbarHostState(snackbarMessage: SnackbarMessage?): SnackbarHostSt
} ?: return snackbarHostState } ?: return snackbarHostState
val dispatcher = LocalSnackbarDispatcher.current val dispatcher = LocalSnackbarDispatcher.current
LaunchedEffect(snackbarMessageText) { LaunchedEffect(snackbarMessage.id) {
// If the message wasn't already displayed, do it now, and mark it as displayed // 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 // 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 { try {
snackbarHostState.showSnackbar( snackbarHostState.showSnackbar(
message = snackbarMessageText, message = snackbarMessageText,

View file

@ -10,6 +10,7 @@ package io.element.android.libraries.designsystem.utils.snackbar
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarDuration
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import kotlin.random.Random
/** /**
* A message to be displayed in a [Snackbar]. * 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 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 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 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. * @param action The action to be performed when the action is clicked.
*/ */
data class SnackbarMessage( data class SnackbarMessage(
@ -24,5 +26,6 @@ data class SnackbarMessage(
val duration: SnackbarDuration = SnackbarDuration.Short, val duration: SnackbarDuration = SnackbarDuration.Short,
@StringRes val actionResId: Int? = null, @StringRes val actionResId: Int? = null,
val isDisplayed: AtomicBoolean = AtomicBoolean(false), val isDisplayed: AtomicBoolean = AtomicBoolean(false),
val id: Long = Random.nextLong(),
val action: () -> Unit = {}, val action: () -> Unit = {},
) )

View file

@ -86,11 +86,13 @@ class MediaGalleryPresenter @AssistedInject constructor(
mediaGalleryDataSource.deleteItem(event.eventId) mediaGalleryDataSource.deleteItem(event.eventId)
} }
is MediaGalleryEvents.SaveOnDisk -> coroutineScope.launch { is MediaGalleryEvents.SaveOnDisk -> coroutineScope.launch {
mediaBottomSheetState = MediaBottomSheetState.Hidden
groupedMediaItems.dataOrNull().find(event.eventId)?.let { groupedMediaItems.dataOrNull().find(event.eventId)?.let {
saveOnDisk(it) saveOnDisk(it)
} }
} }
is MediaGalleryEvents.Share -> coroutineScope.launch { is MediaGalleryEvents.Share -> coroutineScope.launch {
mediaBottomSheetState = MediaBottomSheetState.Hidden
groupedMediaItems.dataOrNull().find(event.eventId)?.let { groupedMediaItems.dataOrNull().find(event.eventId)?.let {
share(it) share(it)
} }

View file

@ -44,7 +44,6 @@ class MediaViewerPresenter @AssistedInject constructor(
@Assisted private val dataSource: MediaViewerDataSource, @Assisted private val dataSource: MediaViewerDataSource,
private val room: MatrixRoom, private val room: MatrixRoom,
private val localMediaActions: LocalMediaActions, private val localMediaActions: LocalMediaActions,
private val snackbarDispatcher: SnackbarDispatcher,
) : Presenter<MediaViewerState> { ) : Presenter<MediaViewerState> {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
@ -55,6 +54,9 @@ class MediaViewerPresenter @AssistedInject constructor(
): MediaViewerPresenter ): MediaViewerPresenter
} }
// Use a local snackbarDispatcher because this presenter is used in an Overlay Node
private val snackbarDispatcher = SnackbarDispatcher()
@Composable @Composable
override fun present(): MediaViewerState { override fun present(): MediaViewerState {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()

View file

@ -13,7 +13,6 @@ import android.net.Uri
import app.cash.turbine.ReceiveTurbine import app.cash.turbine.ReceiveTurbine
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncData 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.core.EventId
import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
@ -568,7 +567,6 @@ class MediaViewerPresenterTest {
eventId: EventId? = null, eventId: EventId? = null,
matrixMediaLoader: FakeMatrixMediaLoader = FakeMatrixMediaLoader(), matrixMediaLoader: FakeMatrixMediaLoader = FakeMatrixMediaLoader(),
localMediaActions: FakeLocalMediaActions = FakeLocalMediaActions(), localMediaActions: FakeLocalMediaActions = FakeLocalMediaActions(),
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
mediaGalleryDataSource: MediaGalleryDataSource = FakeMediaGalleryDataSource( mediaGalleryDataSource: MediaGalleryDataSource = FakeMediaGalleryDataSource(
startLambda = { }, startLambda = { },
), ),
@ -598,7 +596,6 @@ class MediaViewerPresenterTest {
), ),
room = room, room = room,
localMediaActions = localMediaActions, localMediaActions = localMediaActions,
snackbarDispatcher = snackbarDispatcher,
) )
} }
} }