diff --git a/libraries/mediaviewer/impl/build.gradle.kts b/libraries/mediaviewer/impl/build.gradle.kts index deeffe9e8b..d77df8ab36 100644 --- a/libraries/mediaviewer/impl/build.gradle.kts +++ b/libraries/mediaviewer/impl/build.gradle.kts @@ -33,7 +33,6 @@ dependencies { implementation(libs.vanniktech.blurhash) implementation(libs.telephoto.flick) - implementation(projects.features.networkmonitor.api) implementation(projects.libraries.androidutils) implementation(projects.libraries.architecture) implementation(projects.libraries.core) @@ -52,7 +51,6 @@ dependencies { implementation(projects.libraries.di) implementation(projects.libraries.matrix.api) - testImplementation(projects.features.networkmonitor.test) testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.mediaviewer.test) 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 dc973be624..7a70a77e0b 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 @@ -9,6 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import android.content.ActivityNotFoundException import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf @@ -50,7 +51,6 @@ import kotlinx.coroutines.launch class MediaGalleryPresenter @AssistedInject constructor( @Assisted private val navigator: MediaGalleryNavigator, private val room: MatrixRoom, - private val timelineProvider: MediaGalleryTimelineProvider, private val timelineMediaItemsFactory: TimelineMediaItemsFactory, private val localMediaFactory: LocalMediaFactory, private val mediaLoader: MatrixMediaLoader, @@ -97,27 +97,36 @@ class MediaGalleryPresenter @AssistedInject constructor( val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() localMediaActions.Configure() + var timeline by remember { mutableStateOf>(AsyncData.Uninitialized) } + LaunchedEffect(Unit) { + room.mediaTimeline() + .fold( + { timeline = AsyncData.Success(it) }, + { timeline = AsyncData.Failure(it) }, + ) + } + DisposableEffect(Unit) { + onDispose { + timeline.dataOrNull()?.close() + } + } + MediaListEffect( + timeline = timeline, onItemsChange = { newItems -> mediaItems = newItems } ) - LaunchedEffect(Unit) { - timelineProvider.launchIn(this) - } - fun handleEvents(event: MediaGalleryEvents) { when (event) { is MediaGalleryEvents.ChangeMode -> { mode = event.mode } is MediaGalleryEvents.LoadMore -> coroutineScope.launch { - timelineProvider.invokeOnTimeline { - paginate(event.direction) - } + timeline.dataOrNull()?.paginate(event.direction) } - is MediaGalleryEvents.Delete -> coroutineScope.delete(event.eventId) + is MediaGalleryEvents.Delete -> coroutineScope.delete(timeline, event.eventId) is MediaGalleryEvents.SaveOnDisk -> coroutineScope.saveOnDisk(event.mediaItem) is MediaGalleryEvents.Share -> coroutineScope.share(event.mediaItem) is MediaGalleryEvents.ViewInTimeline -> { @@ -166,18 +175,19 @@ class MediaGalleryPresenter @AssistedInject constructor( } @Composable - private fun MediaListEffect(onItemsChange: (AsyncData>) -> Unit) { + private fun MediaListEffect( + timeline: AsyncData, + onItemsChange: (AsyncData>) -> Unit, + ) { val updatedOnItemsChange by rememberUpdatedState(onItemsChange) - val timelineState by timelineProvider.timelineStateFlow.collectAsState() - - LaunchedEffect(timelineState) { - when (val asyncTimeline = timelineState) { + LaunchedEffect(timeline) { + when (timeline) { AsyncData.Uninitialized -> flowOf(AsyncData.Uninitialized) - is AsyncData.Failure -> flowOf(AsyncData.Failure(asyncTimeline.error)) + is AsyncData.Failure -> flowOf(AsyncData.Failure(timeline.error)) is AsyncData.Loading -> flowOf(AsyncData.Loading()) is AsyncData.Success -> { - asyncTimeline.data.timelineItems + timeline.data.timelineItems .onEach { items -> timelineMediaItemsFactory.replaceWith( timelineItems = items, @@ -185,7 +195,7 @@ class MediaGalleryPresenter @AssistedInject constructor( } .launchIn(this) - asyncTimeline.data.paginationStatus(Timeline.PaginationDirection.BACKWARDS) + timeline.data.paginationStatus(Timeline.PaginationDirection.BACKWARDS) .onEach { backwardPaginationStatus -> if (backwardPaginationStatus.canPaginate) { timelineMediaItemsFactory.onCanPaginate() @@ -205,13 +215,14 @@ class MediaGalleryPresenter @AssistedInject constructor( } } - private fun CoroutineScope.delete(eventId: EventId) = launch { - timelineProvider.invokeOnTimeline { - redactEvent( - eventOrTransactionId = eventId.toEventOrTransactionId(), - reason = null, - ) - } + private fun CoroutineScope.delete( + timeline: AsyncData, + eventId: EventId, + ) = launch { + timeline.dataOrNull()?.redactEvent( + eventOrTransactionId = eventId.toEventOrTransactionId(), + reason = null, + ) } private suspend fun downloadMedia(mediaItem: MediaItem.Event): Result { diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryTimelineProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryTimelineProvider.kt deleted file mode 100644 index 9174cab8ca..0000000000 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryTimelineProvider.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.libraries.mediaviewer.impl.gallery - -import io.element.android.features.networkmonitor.api.NetworkMonitor -import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.core.coroutine.mapState -import io.element.android.libraries.di.RoomScope -import io.element.android.libraries.di.SingleIn -import io.element.android.libraries.featureflag.api.FeatureFlagService -import io.element.android.libraries.featureflag.api.FeatureFlags -import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.api.timeline.Timeline -import io.element.android.libraries.matrix.api.timeline.TimelineProvider -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import javax.inject.Inject - -@SingleIn(RoomScope::class) -class MediaGalleryTimelineProvider @Inject constructor( - private val room: MatrixRoom, - private val networkMonitor: NetworkMonitor, - private val featureFlagService: FeatureFlagService, -) : TimelineProvider { - private val _timelineStateFlow: MutableStateFlow> = - MutableStateFlow(AsyncData.Uninitialized) - - override fun activeTimelineFlow(): StateFlow { - return _timelineStateFlow - .mapState { value -> - value.dataOrNull() - } - } - - val timelineStateFlow = _timelineStateFlow - - fun launchIn(scope: CoroutineScope) { - _timelineStateFlow.subscriptionCount - .map { count -> count > 0 } - .distinctUntilChanged() - .onEach { isActive -> - if (isActive) { - onActive() - } else { - onInactive() - } - } - .launchIn(scope) - } - - private suspend fun onActive() = coroutineScope { - combine( - featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaGallery), - networkMonitor.connectivity - ) { isEnabled, _ -> - // do not use connectivity here as data can be loaded from cache, it's just to trigger retry if needed - isEnabled - } - .onEach { isFeatureEnabled -> - if (isFeatureEnabled) { - loadTimelineIfNeeded() - } else { - resetTimeline() - } - } - .launchIn(this) - } - - private suspend fun onInactive() { - resetTimeline() - } - - private suspend fun resetTimeline() { - invokeOnTimeline { - close() - } - _timelineStateFlow.emit(AsyncData.Uninitialized) - } - - suspend fun invokeOnTimeline(action: suspend Timeline.() -> Unit) { - when (val asyncTimeline = timelineStateFlow.value) { - is AsyncData.Success -> action(asyncTimeline.data) - else -> Unit - } - } - - private suspend fun loadTimelineIfNeeded() { - when (timelineStateFlow.value) { - is AsyncData.Uninitialized, - is AsyncData.Failure -> { - timelineStateFlow.emit(AsyncData.Loading()) - room.mediaTimeline() - .fold( - { timelineStateFlow.emit(AsyncData.Success(it)) }, - { timelineStateFlow.emit(AsyncData.Failure(it)) } - ) - } - else -> Unit - } - } -} diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt index c1a71a1152..3acc293f35 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt @@ -9,9 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import android.net.Uri import com.google.common.truth.Truth.assertThat -import io.element.android.features.networkmonitor.test.FakeNetworkMonitor import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher -import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem @@ -242,11 +240,6 @@ class MediaGalleryPresenterTest { return MediaGalleryPresenter( navigator = navigator, room = room, - timelineProvider = MediaGalleryTimelineProvider( - room = room, - networkMonitor = FakeNetworkMonitor(), - featureFlagService = FakeFeatureFlagService(), - ), timelineMediaItemsFactory = FakeTimelineMediaItemsFactory( replaceWithLambda = lambdaRecorder, Unit> { _ -> }, onCanPaginateLambda = lambdaRecorder { },