Move bottom state to the View state

This commit is contained in:
Benoit Marty 2024-12-10 13:34:44 +01:00
parent b3648c1d18
commit d86430d56a
6 changed files with 58 additions and 38 deletions

View file

@ -16,5 +16,8 @@ sealed interface MediaViewerEvents {
data object RetryLoading : MediaViewerEvents data object RetryLoading : MediaViewerEvents
data object ClearLoadingError : MediaViewerEvents data object ClearLoadingError : MediaViewerEvents
data class ViewInTimeline(val eventId: EventId) : MediaViewerEvents data class ViewInTimeline(val eventId: EventId) : MediaViewerEvents
data object OpenInfo : MediaViewerEvents
data class ConfirmDelete(val eventId: EventId) : MediaViewerEvents
data object CloseBottomSheet : MediaViewerEvents
data class Delete(val eventId: EventId) : MediaViewerEvents data class Delete(val eventId: EventId) : MediaViewerEvents
} }

View file

@ -11,11 +11,9 @@ import android.content.ActivityNotFoundException
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -37,6 +35,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTran
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.LocalMedia
import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory
import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState
import io.element.android.libraries.mediaviewer.impl.local.LocalMediaActions import io.element.android.libraries.mediaviewer.impl.local.LocalMediaActions
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -78,15 +77,7 @@ class MediaViewerPresenter @AssistedInject constructor(
mediaFile.value?.close() mediaFile.value?.close()
} }
} }
var mediaBottomSheetState by remember { mutableStateOf<MediaBottomSheetState>(MediaBottomSheetState.Hidden) }
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
val canDelete by produceState(false, syncUpdateFlow.value) {
value = when (inputs.mediaInfo.senderId) {
null -> false
room.sessionId -> room.canRedactOwn().getOrElse { false } && inputs.eventId != null
else -> room.canRedactOther().getOrElse { false } && inputs.eventId != null
}
}
fun handleEvents(mediaViewerEvents: MediaViewerEvents) { fun handleEvents(mediaViewerEvents: MediaViewerEvents) {
when (mediaViewerEvents) { when (mediaViewerEvents) {
@ -95,10 +86,36 @@ class MediaViewerPresenter @AssistedInject constructor(
MediaViewerEvents.SaveOnDisk -> coroutineScope.saveOnDisk(localMedia.value) MediaViewerEvents.SaveOnDisk -> coroutineScope.saveOnDisk(localMedia.value)
MediaViewerEvents.Share -> coroutineScope.share(localMedia.value) MediaViewerEvents.Share -> coroutineScope.share(localMedia.value)
MediaViewerEvents.OpenWith -> coroutineScope.open(localMedia.value) MediaViewerEvents.OpenWith -> coroutineScope.open(localMedia.value)
is MediaViewerEvents.Delete -> coroutineScope.delete(mediaViewerEvents.eventId) is MediaViewerEvents.Delete -> {
mediaBottomSheetState = MediaBottomSheetState.Hidden
coroutineScope.delete(mediaViewerEvents.eventId)
}
is MediaViewerEvents.ViewInTimeline -> { is MediaViewerEvents.ViewInTimeline -> {
mediaBottomSheetState = MediaBottomSheetState.Hidden
navigator.onViewInTimelineClick(mediaViewerEvents.eventId) navigator.onViewInTimelineClick(mediaViewerEvents.eventId)
} }
MediaViewerEvents.OpenInfo -> coroutineScope.launch {
mediaBottomSheetState = MediaBottomSheetState.MediaDetailsBottomSheetState(
eventId = inputs.eventId,
canDelete = when (inputs.mediaInfo.senderId) {
null -> false
room.sessionId -> room.canRedactOwn().getOrElse { false } && inputs.eventId != null
else -> room.canRedactOther().getOrElse { false } && inputs.eventId != null
},
mediaInfo = inputs.mediaInfo,
thumbnailSource = inputs.thumbnailSource,
)
}
is MediaViewerEvents.ConfirmDelete -> {
mediaBottomSheetState = MediaBottomSheetState.MediaDeleteConfirmationState(
eventId = mediaViewerEvents.eventId,
mediaInfo = inputs.mediaInfo,
thumbnailSource = inputs.thumbnailSource ?: inputs.mediaSource,
)
}
MediaViewerEvents.CloseBottomSheet -> {
mediaBottomSheetState = MediaBottomSheetState.Hidden
}
} }
} }
@ -111,7 +128,7 @@ class MediaViewerPresenter @AssistedInject constructor(
canShowInfo = inputs.canShowInfo, canShowInfo = inputs.canShowInfo,
canDownload = inputs.canDownload, canDownload = inputs.canDownload,
canShare = inputs.canShare, canShare = inputs.canShare,
canDelete = canDelete, mediaBottomSheetState = mediaBottomSheetState,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }

View file

@ -13,6 +13,7 @@ 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.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.MediaInfo
import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.LocalMedia
import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState
data class MediaViewerState( data class MediaViewerState(
val eventId: EventId?, val eventId: EventId?,
@ -23,6 +24,6 @@ data class MediaViewerState(
val canShowInfo: Boolean, val canShowInfo: Boolean,
val canDownload: Boolean, val canDownload: Boolean,
val canShare: Boolean, val canShare: Boolean,
val canDelete: Boolean, val mediaBottomSheetState: MediaBottomSheetState,
val eventSink: (MediaViewerEvents) -> Unit, val eventSink: (MediaViewerEvents) -> Unit,
) )

View file

@ -17,6 +17,9 @@ import io.element.android.libraries.mediaviewer.api.anApkMediaInfo
import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo
import io.element.android.libraries.mediaviewer.api.anImageMediaInfo import io.element.android.libraries.mediaviewer.api.anImageMediaInfo
import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.LocalMedia
import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState
import io.element.android.libraries.mediaviewer.impl.details.aMediaDeleteConfirmationState
import io.element.android.libraries.mediaviewer.impl.details.aMediaDetailsBottomSheetState
open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState> { open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState> {
override val values: Sequence<MediaViewerState> override val values: Sequence<MediaViewerState>
@ -91,6 +94,12 @@ open class MediaViewerStateProvider : PreviewParameterProvider<MediaViewerState>
canShare = false, canShare = false,
) )
}, },
aMediaViewerState(
mediaBottomSheetState = aMediaDetailsBottomSheetState(),
),
aMediaViewerState(
mediaBottomSheetState = aMediaDeleteConfirmationState(),
),
) )
} }
@ -100,6 +109,7 @@ fun aMediaViewerState(
canShowInfo: Boolean = true, canShowInfo: Boolean = true,
canDownload: Boolean = true, canDownload: Boolean = true,
canShare: Boolean = true, canShare: Boolean = true,
mediaBottomSheetState: MediaBottomSheetState = MediaBottomSheetState.Hidden,
eventSink: (MediaViewerEvents) -> Unit = {}, eventSink: (MediaViewerEvents) -> Unit = {},
) = MediaViewerState( ) = MediaViewerState(
eventId = null, eventId = null,
@ -110,6 +120,6 @@ fun aMediaViewerState(
canShowInfo = canShowInfo, canShowInfo = canShowInfo,
canDownload = canDownload, canDownload = canDownload,
canShare = canShare, canShare = canShare,
canDelete = true, mediaBottomSheetState = mediaBottomSheetState,
eventSink = eventSink, eventSink = eventSink,
) )

View file

@ -95,7 +95,6 @@ fun MediaViewerView(
val defaultBottomPaddingInPixels = if (LocalInspectionMode.current) 303 else 0 val defaultBottomPaddingInPixels = if (LocalInspectionMode.current) 303 else 0
var bottomPaddingInPixels by remember { mutableIntStateOf(defaultBottomPaddingInPixels) } var bottomPaddingInPixels by remember { mutableIntStateOf(defaultBottomPaddingInPixels) }
BackHandler { onBackClick() } BackHandler { onBackClick() }
var mediaBottomSheetState by remember { mutableStateOf<MediaBottomSheetState>(MediaBottomSheetState.Hidden) }
Scaffold( Scaffold(
modifier, modifier,
containerColor = Color.Transparent, containerColor = Color.Transparent,
@ -128,12 +127,7 @@ fun MediaViewerView(
canShowInfo = state.canShowInfo, canShowInfo = state.canShowInfo,
onBackClick = onBackClick, onBackClick = onBackClick,
onInfoClick = { onInfoClick = {
mediaBottomSheetState = MediaBottomSheetState.MediaDetailsBottomSheetState( state.eventSink(MediaViewerEvents.OpenInfo)
eventId = state.eventId,
canDelete = state.canDelete,
mediaInfo = state.mediaInfo,
thumbnailSource = state.thumbnailSource,
)
}, },
eventSink = state.eventSink eventSink = state.eventSink
) )
@ -146,24 +140,19 @@ fun MediaViewerView(
} }
} }
} }
when (val bottomSheetState = mediaBottomSheetState) { when (val bottomSheetState = state.mediaBottomSheetState) {
MediaBottomSheetState.Hidden -> Unit MediaBottomSheetState.Hidden -> Unit
is MediaBottomSheetState.MediaDetailsBottomSheetState -> { is MediaBottomSheetState.MediaDetailsBottomSheetState -> {
MediaDetailsBottomSheet( MediaDetailsBottomSheet(
state = bottomSheetState, state = bottomSheetState,
onViewInTimeline = { onViewInTimeline = {
mediaBottomSheetState = MediaBottomSheetState.Hidden
state.eventSink(MediaViewerEvents.ViewInTimeline(it)) state.eventSink(MediaViewerEvents.ViewInTimeline(it))
}, },
onDelete = { eventId -> onDelete = { eventId ->
mediaBottomSheetState = MediaBottomSheetState.MediaDeleteConfirmationState( state.eventSink(MediaViewerEvents.ConfirmDelete(eventId))
eventId = eventId,
mediaInfo = state.mediaInfo,
thumbnailSource = state.thumbnailSource,
)
}, },
onDismiss = { onDismiss = {
mediaBottomSheetState = MediaBottomSheetState.Hidden state.eventSink(MediaViewerEvents.CloseBottomSheet)
}, },
) )
} }
@ -171,11 +160,10 @@ fun MediaViewerView(
MediaDeleteConfirmationBottomSheet( MediaDeleteConfirmationBottomSheet(
state = bottomSheetState, state = bottomSheetState,
onDelete = { onDelete = {
mediaBottomSheetState = MediaBottomSheetState.Hidden
state.eventSink(MediaViewerEvents.Delete(it)) state.eventSink(MediaViewerEvents.Delete(it))
}, },
onDismiss = { onDismiss = {
mediaBottomSheetState = MediaBottomSheetState.Hidden state.eventSink(MediaViewerEvents.CloseBottomSheet)
}, },
) )
} }

View file

@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint
import io.element.android.libraries.mediaviewer.api.anApkMediaInfo import io.element.android.libraries.mediaviewer.api.anApkMediaInfo
import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState
import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions
import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
@ -67,7 +68,7 @@ class MediaViewerPresenterTest {
assertThat(initialState.canShowInfo).isTrue() assertThat(initialState.canShowInfo).isTrue()
assertThat(initialState.canDownload).isTrue() assertThat(initialState.canDownload).isTrue()
assertThat(initialState.canShare).isTrue() assertThat(initialState.canShare).isTrue()
assertThat(initialState.canDelete).isFalse() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden)
} }
} }
@ -87,7 +88,7 @@ class MediaViewerPresenterTest {
assertThat(initialState.canShowInfo).isFalse() assertThat(initialState.canShowInfo).isFalse()
assertThat(initialState.canDownload).isTrue() assertThat(initialState.canDownload).isTrue()
assertThat(initialState.canShare).isTrue() assertThat(initialState.canShare).isTrue()
assertThat(initialState.canDelete).isFalse() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden)
} }
} }
@ -107,7 +108,7 @@ class MediaViewerPresenterTest {
assertThat(initialState.canShowInfo).isTrue() assertThat(initialState.canShowInfo).isTrue()
assertThat(initialState.canDownload).isTrue() assertThat(initialState.canDownload).isTrue()
assertThat(initialState.canShare).isFalse() assertThat(initialState.canShare).isFalse()
assertThat(initialState.canDelete).isFalse() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden)
} }
} }
@ -127,7 +128,7 @@ class MediaViewerPresenterTest {
assertThat(initialState.canShowInfo).isTrue() assertThat(initialState.canShowInfo).isTrue()
assertThat(initialState.canDownload).isFalse() assertThat(initialState.canDownload).isFalse()
assertThat(initialState.canShare).isTrue() assertThat(initialState.canShare).isTrue()
assertThat(initialState.canDelete).isFalse() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden)
} }
} }
@ -147,7 +148,7 @@ class MediaViewerPresenterTest {
assertThat(initialState.canShowInfo).isTrue() assertThat(initialState.canShowInfo).isTrue()
assertThat(initialState.canDownload).isTrue() assertThat(initialState.canDownload).isTrue()
assertThat(initialState.canShare).isTrue() assertThat(initialState.canShare).isTrue()
assertThat(initialState.canDelete).isTrue() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden)
} }
} }
@ -168,7 +169,7 @@ class MediaViewerPresenterTest {
assertThat(initialState.canShowInfo).isTrue() assertThat(initialState.canShowInfo).isTrue()
assertThat(initialState.canDownload).isTrue() assertThat(initialState.canDownload).isTrue()
assertThat(initialState.canShare).isTrue() assertThat(initialState.canShare).isTrue()
assertThat(initialState.canDelete).isFalse() assertThat(initialState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden)
} }
} }