Remove not needed MediaGalleryTimelineProvider
This commit is contained in:
parent
6433239837
commit
dbb0996725
4 changed files with 35 additions and 146 deletions
|
|
@ -33,7 +33,6 @@ dependencies {
|
||||||
implementation(libs.vanniktech.blurhash)
|
implementation(libs.vanniktech.blurhash)
|
||||||
implementation(libs.telephoto.flick)
|
implementation(libs.telephoto.flick)
|
||||||
|
|
||||||
implementation(projects.features.networkmonitor.api)
|
|
||||||
implementation(projects.libraries.androidutils)
|
implementation(projects.libraries.androidutils)
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
|
|
@ -52,7 +51,6 @@ dependencies {
|
||||||
implementation(projects.libraries.di)
|
implementation(projects.libraries.di)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
|
||||||
testImplementation(projects.features.networkmonitor.test)
|
|
||||||
testImplementation(projects.libraries.featureflag.test)
|
testImplementation(projects.libraries.featureflag.test)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.mediaviewer.test)
|
testImplementation(projects.libraries.mediaviewer.test)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
|
@ -50,7 +51,6 @@ import kotlinx.coroutines.launch
|
||||||
class MediaGalleryPresenter @AssistedInject constructor(
|
class MediaGalleryPresenter @AssistedInject constructor(
|
||||||
@Assisted private val navigator: MediaGalleryNavigator,
|
@Assisted private val navigator: MediaGalleryNavigator,
|
||||||
private val room: MatrixRoom,
|
private val room: MatrixRoom,
|
||||||
private val timelineProvider: MediaGalleryTimelineProvider,
|
|
||||||
private val timelineMediaItemsFactory: TimelineMediaItemsFactory,
|
private val timelineMediaItemsFactory: TimelineMediaItemsFactory,
|
||||||
private val localMediaFactory: LocalMediaFactory,
|
private val localMediaFactory: LocalMediaFactory,
|
||||||
private val mediaLoader: MatrixMediaLoader,
|
private val mediaLoader: MatrixMediaLoader,
|
||||||
|
|
@ -97,27 +97,36 @@ class MediaGalleryPresenter @AssistedInject constructor(
|
||||||
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
||||||
localMediaActions.Configure()
|
localMediaActions.Configure()
|
||||||
|
|
||||||
|
var timeline by remember { mutableStateOf<AsyncData<Timeline>>(AsyncData.Uninitialized) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
room.mediaTimeline()
|
||||||
|
.fold(
|
||||||
|
{ timeline = AsyncData.Success(it) },
|
||||||
|
{ timeline = AsyncData.Failure(it) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
timeline.dataOrNull()?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MediaListEffect(
|
MediaListEffect(
|
||||||
|
timeline = timeline,
|
||||||
onItemsChange = { newItems ->
|
onItemsChange = { newItems ->
|
||||||
mediaItems = newItems
|
mediaItems = newItems
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
timelineProvider.launchIn(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleEvents(event: MediaGalleryEvents) {
|
fun handleEvents(event: MediaGalleryEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is MediaGalleryEvents.ChangeMode -> {
|
is MediaGalleryEvents.ChangeMode -> {
|
||||||
mode = event.mode
|
mode = event.mode
|
||||||
}
|
}
|
||||||
is MediaGalleryEvents.LoadMore -> coroutineScope.launch {
|
is MediaGalleryEvents.LoadMore -> coroutineScope.launch {
|
||||||
timelineProvider.invokeOnTimeline {
|
timeline.dataOrNull()?.paginate(event.direction)
|
||||||
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.SaveOnDisk -> coroutineScope.saveOnDisk(event.mediaItem)
|
||||||
is MediaGalleryEvents.Share -> coroutineScope.share(event.mediaItem)
|
is MediaGalleryEvents.Share -> coroutineScope.share(event.mediaItem)
|
||||||
is MediaGalleryEvents.ViewInTimeline -> {
|
is MediaGalleryEvents.ViewInTimeline -> {
|
||||||
|
|
@ -166,18 +175,19 @@ class MediaGalleryPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MediaListEffect(onItemsChange: (AsyncData<ImmutableList<MediaItem>>) -> Unit) {
|
private fun MediaListEffect(
|
||||||
|
timeline: AsyncData<Timeline>,
|
||||||
|
onItemsChange: (AsyncData<ImmutableList<MediaItem>>) -> Unit,
|
||||||
|
) {
|
||||||
val updatedOnItemsChange by rememberUpdatedState(onItemsChange)
|
val updatedOnItemsChange by rememberUpdatedState(onItemsChange)
|
||||||
|
|
||||||
val timelineState by timelineProvider.timelineStateFlow.collectAsState()
|
LaunchedEffect(timeline) {
|
||||||
|
when (timeline) {
|
||||||
LaunchedEffect(timelineState) {
|
|
||||||
when (val asyncTimeline = timelineState) {
|
|
||||||
AsyncData.Uninitialized -> flowOf(AsyncData.Uninitialized)
|
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.Loading -> flowOf(AsyncData.Loading())
|
||||||
is AsyncData.Success -> {
|
is AsyncData.Success -> {
|
||||||
asyncTimeline.data.timelineItems
|
timeline.data.timelineItems
|
||||||
.onEach { items ->
|
.onEach { items ->
|
||||||
timelineMediaItemsFactory.replaceWith(
|
timelineMediaItemsFactory.replaceWith(
|
||||||
timelineItems = items,
|
timelineItems = items,
|
||||||
|
|
@ -185,7 +195,7 @@ class MediaGalleryPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
|
|
||||||
asyncTimeline.data.paginationStatus(Timeline.PaginationDirection.BACKWARDS)
|
timeline.data.paginationStatus(Timeline.PaginationDirection.BACKWARDS)
|
||||||
.onEach { backwardPaginationStatus ->
|
.onEach { backwardPaginationStatus ->
|
||||||
if (backwardPaginationStatus.canPaginate) {
|
if (backwardPaginationStatus.canPaginate) {
|
||||||
timelineMediaItemsFactory.onCanPaginate()
|
timelineMediaItemsFactory.onCanPaginate()
|
||||||
|
|
@ -205,13 +215,14 @@ class MediaGalleryPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.delete(eventId: EventId) = launch {
|
private fun CoroutineScope.delete(
|
||||||
timelineProvider.invokeOnTimeline {
|
timeline: AsyncData<Timeline>,
|
||||||
redactEvent(
|
eventId: EventId,
|
||||||
eventOrTransactionId = eventId.toEventOrTransactionId(),
|
) = launch {
|
||||||
reason = null,
|
timeline.dataOrNull()?.redactEvent(
|
||||||
)
|
eventOrTransactionId = eventId.toEventOrTransactionId(),
|
||||||
}
|
reason = null,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun downloadMedia(mediaItem: MediaItem.Event): Result<LocalMedia> {
|
private suspend fun downloadMedia(mediaItem: MediaItem.Event): Result<LocalMedia> {
|
||||||
|
|
|
||||||
|
|
@ -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<AsyncData<Timeline>> =
|
|
||||||
MutableStateFlow(AsyncData.Uninitialized)
|
|
||||||
|
|
||||||
override fun activeTimelineFlow(): StateFlow<Timeline?> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,9 +9,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.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.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||||
|
|
@ -242,11 +240,6 @@ class MediaGalleryPresenterTest {
|
||||||
return MediaGalleryPresenter(
|
return MediaGalleryPresenter(
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
room = room,
|
room = room,
|
||||||
timelineProvider = MediaGalleryTimelineProvider(
|
|
||||||
room = room,
|
|
||||||
networkMonitor = FakeNetworkMonitor(),
|
|
||||||
featureFlagService = FakeFeatureFlagService(),
|
|
||||||
),
|
|
||||||
timelineMediaItemsFactory = FakeTimelineMediaItemsFactory(
|
timelineMediaItemsFactory = FakeTimelineMediaItemsFactory(
|
||||||
replaceWithLambda = lambdaRecorder<List<MatrixTimelineItem>, Unit> { _ -> },
|
replaceWithLambda = lambdaRecorder<List<MatrixTimelineItem>, Unit> { _ -> },
|
||||||
onCanPaginateLambda = lambdaRecorder<Unit> { },
|
onCanPaginateLambda = lambdaRecorder<Unit> { },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue