Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.core.extensions.mapCatchingExceptions
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaFile
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint.MediaViewerMode
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
Expand All @@ -40,6 +41,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap

class MediaViewerDataSource(
mode: MediaViewerMode,
Expand All @@ -51,7 +53,7 @@ class MediaViewerDataSource(
private val pagerKeysHandler: PagerKeysHandler,
) {
// List of media files that are currently being loaded
private val mediaFiles: MutableList<MediaFile> = mutableListOf()
private val mediaFiles: ConcurrentHashMap<MediaSource, MediaFile> = ConcurrentHashMap()

private val galleryMode = when (mode) {
MediaViewerMode.SingleMedia,
Expand All @@ -69,7 +71,7 @@ class MediaViewerDataSource(

fun dispose() {
Timber.d("Disposing MediaViewerDataSource, closing ${mediaFiles.size} media files")
mediaFiles.forEach { it.close() }
mediaFiles.values.forEach { it.close() }
mediaFiles.clear()
localMediaStates.clear()
}
Expand Down Expand Up @@ -163,6 +165,12 @@ class MediaViewerDataSource(
}

suspend fun loadMedia(data: MediaViewerPageData.MediaViewerData) {
val currentState = localMediaStates[data.mediaSource.safeUrl]?.value
// If the media is already loading or has been loaded successfully, do nothing
if (currentState?.isLoading() == true || currentState?.isSuccess() == true) {
return
}

Timber.d("loadMedia for ${data.eventId}")
val localMediaState = localMediaStates.getOrPut(data.mediaSource.safeUrl) {
mutableStateOf(AsyncData.Uninitialized)
Expand All @@ -175,7 +183,7 @@ class MediaViewerDataSource(
filename = data.mediaInfo.filename
)
.onSuccess { mediaFile ->
mediaFiles.add(mediaFile)
mediaFiles[data.mediaSource] = mediaFile
}
.mapCatchingExceptions { mediaFile ->
localMediaFactory.createFromMediaFile(
Expand All @@ -190,4 +198,12 @@ class MediaViewerDataSource(
localMediaState.value = AsyncData.Failure(it)
}
}

fun cancelLoadingMedia(data: MediaViewerPageData.MediaViewerData) {
if (localMediaStates[data.mediaSource.safeUrl]?.value?.isLoading() == true) {
Timber.d("cancelLoadingMedia for ${data.eventId}")
mediaFiles.remove(data.mediaSource)?.close()
localMediaStates[data.mediaSource.safeUrl]?.value = AsyncData.Uninitialized
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ sealed interface MediaViewerEvent {
data class Delete(val eventId: EventId) : MediaViewerEvent
data class OnNavigateTo(val index: Int) : MediaViewerEvent
data class LoadMore(val direction: Timeline.PaginationDirection) : MediaViewerEvent
data class CancelLoadingMedia(val data: MediaViewerPageData.MediaViewerData) : MediaViewerEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ class MediaViewerPresenter(
is MediaViewerEvent.LoadMedia -> {
coroutineScope.downloadMedia(data = event.data)
}
is MediaViewerEvent.CancelLoadingMedia -> {
dataSource.cancelLoadingMedia(event.data)
}
is MediaViewerEvent.ClearLoadingError -> {
dataSource.clearLoadingError(event.data)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.onVisibilityChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource
Expand Down Expand Up @@ -208,11 +209,16 @@ fun MediaViewerView(
}
is MediaViewerPageData.MediaViewerData -> {
var bottomPaddingInPixels by remember { mutableIntStateOf(defaultBottomPaddingInPixels) }
LaunchedEffect(Unit) {
state.eventSink(MediaViewerEvent.LoadMedia(dataForPage))
}
Box(
modifier = Modifier.fillMaxSize()
modifier = Modifier
.onVisibilityChanged(minDurationMs = 200L) { isVisible ->
if (isVisible) {
state.eventSink(MediaViewerEvent.LoadMedia(dataForPage))
} else {
state.eventSink(MediaViewerEvent.CancelLoadingMedia(dataForPage))
}
}
.fillMaxSize()
) {
val isDisplayed = remember(pagerState.settledPage) {
// This 'item provider' lambda will be called when the data source changes with an outdated `settlePage` value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class MediaViewerViewTest {
state = state,
onBackClick = callback,
)

// Wait for enough time for the onVisibilityChanged modifier to trigger
mainClock.advanceTimeBy(200)

pressBack()
}
eventsRecorder.assertList(
Expand Down Expand Up @@ -110,6 +114,10 @@ class MediaViewerViewTest {
eventSink = eventsRecorder
),
)

// Wait for enough time for the onVisibilityChanged modifier to trigger
mainClock.advanceTimeBy(200)

val contentDescription = activity!!.getString(contentDescriptionRes)
onNodeWithContentDescription(contentDescription).performClick()
eventsRecorder.assertList(
Expand Down Expand Up @@ -241,6 +249,10 @@ class MediaViewerViewTest {
eventSink = eventsRecorder
),
)

// Wait for enough time for the onVisibilityChanged modifier to trigger
mainClock.advanceTimeBy(200)

clickOn(CommonStrings.action_retry)
eventsRecorder.assertList(
listOf(
Expand All @@ -263,6 +275,10 @@ class MediaViewerViewTest {
eventSink = eventsRecorder
),
)

// Wait for enough time for the onVisibilityChanged modifier to trigger
mainClock.advanceTimeBy(200)

clickOn(CommonStrings.action_cancel)
eventsRecorder.assertList(
listOf(
Expand Down
Loading