Reimplement "Natural media viewer swiping order" (#6715)
This commit is contained in:
parent
6abb83547b
commit
8e8426b08c
4 changed files with 33 additions and 56 deletions
|
|
@ -122,25 +122,11 @@ class MediaViewerDataSource(
|
||||||
*/
|
*/
|
||||||
private fun buildMediaViewerPageList(groupedItems: List<MediaItem>) = buildList {
|
private fun buildMediaViewerPageList(groupedItems: List<MediaItem>) = buildList {
|
||||||
// Filter out DateSeparator items, we do not need them for the media viewer
|
// Filter out DateSeparator items, we do not need them for the media viewer
|
||||||
val itemsNoDateSeparator = groupedItems.filterNot { it is MediaItem.DateSeparator }
|
val groupedItemsNoDateSeparator = groupedItems.filterNot { it is MediaItem.DateSeparator }
|
||||||
// Separate loading indicators and media events
|
pagerKeysHandler.accept(groupedItemsNoDateSeparator)
|
||||||
val loadingIndicators = itemsNoDateSeparator.filterIsInstance<MediaItem.LoadingIndicator>()
|
groupedItemsNoDateSeparator.forEach { mediaItem ->
|
||||||
val mediaEvents = itemsNoDateSeparator.filterIsInstance<MediaItem.Event>()
|
|
||||||
// Determine backward and forward loading indicators
|
|
||||||
val backwardLoading = loadingIndicators.find { it.direction == Timeline.PaginationDirection.BACKWARDS }
|
|
||||||
val forwardLoading = loadingIndicators.find { it.direction == Timeline.PaginationDirection.FORWARDS }
|
|
||||||
// Build ordered list: backward loading, media events (oldest first), forward loading
|
|
||||||
// Media events are currently newest first, reverse to get oldest first
|
|
||||||
val orderedEvents = mediaEvents.reversed()
|
|
||||||
// Create new list of MediaItem in order: backwardLoading, orderedEvents, forwardLoading
|
|
||||||
val orderedItems = buildList {
|
|
||||||
backwardLoading?.let { add(it) }
|
|
||||||
addAll(orderedEvents)
|
|
||||||
forwardLoading?.let { add(it) }
|
|
||||||
}
|
|
||||||
pagerKeysHandler.accept(orderedItems)
|
|
||||||
orderedItems.forEach { mediaItem ->
|
|
||||||
when (mediaItem) {
|
when (mediaItem) {
|
||||||
|
is MediaItem.DateSeparator -> Unit
|
||||||
is MediaItem.Event -> {
|
is MediaItem.Event -> {
|
||||||
val sourceUrl = mediaItem.mediaSource().safeUrl
|
val sourceUrl = mediaItem.mediaSource().safeUrl
|
||||||
val localMedia = localMediaStates.getOrPut(sourceUrl) {
|
val localMedia = localMediaStates.getOrPut(sourceUrl) {
|
||||||
|
|
@ -164,7 +150,6 @@ class MediaViewerDataSource(
|
||||||
pagerKey = pagerKeysHandler.getKey(mediaItem),
|
pagerKey = pagerKeysHandler.getKey(mediaItem),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
is MediaItem.DateSeparator -> Unit // already filtered out
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.toImmutableList()
|
}.toImmutableList()
|
||||||
|
|
|
||||||
|
|
@ -177,21 +177,18 @@ class MediaViewerPresenter(
|
||||||
currentIndex: IntState,
|
currentIndex: IntState,
|
||||||
data: State<ImmutableList<MediaViewerPageData>>,
|
data: State<ImmutableList<MediaViewerPageData>>,
|
||||||
) {
|
) {
|
||||||
|
// With newest-first ordering, backward loading indicator is at the last index
|
||||||
val isRenderingLoadingBackward by remember {
|
val isRenderingLoadingBackward by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
currentIndex.intValue == 0 &&
|
currentIndex.intValue == data.value.lastIndex &&
|
||||||
data.value.size > 1 &&
|
data.value.size > 1 &&
|
||||||
data.value.firstOrNull() is MediaViewerPageData.Loading &&
|
data.value.lastOrNull() is MediaViewerPageData.Loading
|
||||||
(data.value.firstOrNull() as? MediaViewerPageData.Loading)?.direction == Timeline.PaginationDirection.BACKWARDS
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isRenderingLoadingBackward) {
|
if (isRenderingLoadingBackward) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
// Observe the loading data vanishing
|
// Observe the loading data vanishing
|
||||||
snapshotFlow {
|
snapshotFlow { data.value.lastOrNull() is MediaViewerPageData.Loading }
|
||||||
val first = data.value.firstOrNull()
|
|
||||||
first is MediaViewerPageData.Loading && first.direction == Timeline.PaginationDirection.BACKWARDS
|
|
||||||
}
|
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.filter { !it }
|
.filter { !it }
|
||||||
.onEach { showNoMoreItemsSnackbar() }
|
.onEach { showNoMoreItemsSnackbar() }
|
||||||
|
|
@ -205,21 +202,18 @@ class MediaViewerPresenter(
|
||||||
currentIndex: IntState,
|
currentIndex: IntState,
|
||||||
data: State<ImmutableList<MediaViewerPageData>>,
|
data: State<ImmutableList<MediaViewerPageData>>,
|
||||||
) {
|
) {
|
||||||
|
// With newest-first ordering, forward loading indicator is at the first index
|
||||||
val isRenderingLoadingForward by remember {
|
val isRenderingLoadingForward by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
currentIndex.intValue == data.value.lastIndex &&
|
currentIndex.intValue == 0 &&
|
||||||
data.value.size > 1 &&
|
data.value.size > 1 &&
|
||||||
data.value.lastOrNull() is MediaViewerPageData.Loading &&
|
data.value.firstOrNull() is MediaViewerPageData.Loading
|
||||||
(data.value.lastOrNull() as? MediaViewerPageData.Loading)?.direction == Timeline.PaginationDirection.FORWARDS
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isRenderingLoadingForward) {
|
if (isRenderingLoadingForward) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
// Observe the loading data vanishing
|
// Observe the loading data vanishing
|
||||||
snapshotFlow {
|
snapshotFlow { data.value.firstOrNull() is MediaViewerPageData.Loading }
|
||||||
val last = data.value.lastOrNull()
|
|
||||||
last is MediaViewerPageData.Loading && last.direction == Timeline.PaginationDirection.FORWARDS
|
|
||||||
}
|
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.filter { !it }
|
.filter { !it }
|
||||||
.onEach { showNoMoreItemsSnackbar() }
|
.onEach { showNoMoreItemsSnackbar() }
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,7 @@ fun MediaViewerView(
|
||||||
// Pre-load previous and next pages
|
// Pre-load previous and next pages
|
||||||
beyondViewportPageCount = 1,
|
beyondViewportPageCount = 1,
|
||||||
key = { index -> state.listData[index].pagerKey },
|
key = { index -> state.listData[index].pagerKey },
|
||||||
|
reverseLayout = true,
|
||||||
) { page ->
|
) { page ->
|
||||||
when (val dataForPage = state.listData[page]) {
|
when (val dataForPage = state.listData[page]) {
|
||||||
is MediaViewerPageData.Failure -> {
|
is MediaViewerPageData.Failure -> {
|
||||||
|
|
|
||||||
|
|
@ -593,20 +593,20 @@ class MediaViewerPresenterTest {
|
||||||
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(),
|
imageAndVideoItems = persistentListOf(),
|
||||||
fileItems = persistentListOf(aBackwardLoadingIndicator, anImage, aForwardLoadingIndicator),
|
fileItems = persistentListOf(aForwardLoadingIndicator, anImage, aBackwardLoadingIndicator),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(aBackwardLoadingIndicator, anImage, aForwardLoadingIndicator),
|
imageAndVideoItems = persistentListOf(aForwardLoadingIndicator, anImage, aBackwardLoadingIndicator),
|
||||||
fileItems = persistentListOf(),
|
fileItems = persistentListOf(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val updatedState = awaitItem()
|
val updatedState = awaitItem()
|
||||||
// User navigate to the last item (forward loading indicator)
|
// User navigate to the first item (forward loading indicator)
|
||||||
updatedState.eventSink(
|
updatedState.eventSink(
|
||||||
MediaViewerEvent.OnNavigateTo(2)
|
MediaViewerEvent.OnNavigateTo(0)
|
||||||
)
|
)
|
||||||
// data source claims that there is no more items to load forward
|
// data source claims that there is no more items to load forward
|
||||||
mediaGalleryDataSource.emitGroupedMediaItems(
|
mediaGalleryDataSource.emitGroupedMediaItems(
|
||||||
|
|
@ -614,21 +614,19 @@ class MediaViewerPresenterTest {
|
||||||
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(),
|
imageAndVideoItems = persistentListOf(),
|
||||||
fileItems = persistentListOf(aBackwardLoadingIndicator, anImage),
|
fileItems = persistentListOf(anImage, aBackwardLoadingIndicator),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(aBackwardLoadingIndicator, anImage),
|
imageAndVideoItems = persistentListOf(anImage, aBackwardLoadingIndicator),
|
||||||
fileItems = persistentListOf(),
|
fileItems = persistentListOf(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
var stateWithSnackbar = awaitItem()
|
skipItems(1)
|
||||||
while (stateWithSnackbar.snackbarMessage == null) {
|
val stateWithSnackbar = awaitItem()
|
||||||
stateWithSnackbar = awaitItem()
|
assertThat(stateWithSnackbar.snackbarMessage!!.messageResId).isEqualTo(expectedSnackbarResId)
|
||||||
}
|
|
||||||
assertThat(stateWithSnackbar.snackbarMessage.messageResId).isEqualTo(expectedSnackbarResId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -667,42 +665,41 @@ class MediaViewerPresenterTest {
|
||||||
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(),
|
imageAndVideoItems = persistentListOf(),
|
||||||
fileItems = persistentListOf(aBackwardLoadingIndicator, anImage, aForwardLoadingIndicator),
|
fileItems = persistentListOf(aForwardLoadingIndicator, anImage, aBackwardLoadingIndicator),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(aBackwardLoadingIndicator, anImage, aForwardLoadingIndicator),
|
imageAndVideoItems = persistentListOf(aForwardLoadingIndicator, anImage, aBackwardLoadingIndicator),
|
||||||
fileItems = persistentListOf(),
|
fileItems = persistentListOf(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val updatedState = awaitItem()
|
val updatedState = awaitItem()
|
||||||
// User navigate to the first item (backward loading indicator)
|
// User navigate to the last item (backward loading indicator)
|
||||||
updatedState.eventSink(
|
updatedState.eventSink(
|
||||||
MediaViewerEvent.OnNavigateTo(0)
|
MediaViewerEvent.OnNavigateTo(2)
|
||||||
)
|
)
|
||||||
|
skipItems(1)
|
||||||
// data source claims that there is no more items to load backward
|
// data source claims that there is no more items to load backward
|
||||||
mediaGalleryDataSource.emitGroupedMediaItems(
|
mediaGalleryDataSource.emitGroupedMediaItems(
|
||||||
AsyncData.Success(
|
AsyncData.Success(
|
||||||
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
if (mode is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios) {
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(),
|
imageAndVideoItems = persistentListOf(),
|
||||||
fileItems = persistentListOf(anImage, aForwardLoadingIndicator),
|
fileItems = persistentListOf(aForwardLoadingIndicator, anImage),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(anImage, aForwardLoadingIndicator),
|
imageAndVideoItems = persistentListOf(aForwardLoadingIndicator, anImage),
|
||||||
fileItems = persistentListOf(),
|
fileItems = persistentListOf(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
var stateWithSnackbar = awaitItem()
|
skipItems(1)
|
||||||
while (stateWithSnackbar.snackbarMessage == null) {
|
val stateWithSnackbar = awaitItem()
|
||||||
stateWithSnackbar = awaitItem()
|
assertThat(stateWithSnackbar.snackbarMessage!!.messageResId).isEqualTo(expectedSnackbarResId)
|
||||||
}
|
|
||||||
assertThat(stateWithSnackbar.snackbarMessage.messageResId).isEqualTo(expectedSnackbarResId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -720,7 +717,7 @@ class MediaViewerPresenterTest {
|
||||||
mediaGalleryDataSource.emitGroupedMediaItems(
|
mediaGalleryDataSource.emitGroupedMediaItems(
|
||||||
AsyncData.Success(
|
AsyncData.Success(
|
||||||
GroupedMediaItems(
|
GroupedMediaItems(
|
||||||
imageAndVideoItems = persistentListOf(aBackwardLoadingIndicator, anImage, aForwardLoadingIndicator),
|
imageAndVideoItems = persistentListOf(aForwardLoadingIndicator, anImage, aBackwardLoadingIndicator),
|
||||||
fileItems = persistentListOf(),
|
fileItems = persistentListOf(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue