Merge pull request #4195 from element-hq/feature/bma/snackBar
Fix snack bar not displayed in MediaViewer
This commit is contained in:
commit
fa617dbda8
8 changed files with 17 additions and 17 deletions
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 = {},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue