blob: 50f60aea49041474f52db7d97a966626a6360070 [file] [log] [blame]
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.photopicker.extensions
import androidx.paging.PagingData
import androidx.paging.insertSeparators
import androidx.paging.map
import com.android.photopicker.core.components.MediaGridItem
import com.android.photopicker.data.model.Group
import com.android.photopicker.data.model.Media
import java.time.LocalDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/**
* An extension function to prepare a flow of [PagingData<Media>] to be provided to the [MediaGrid]
* composable, by wrapping all of the [Media] objects in a [MediaGridItem.MediaItem].
*
* @return A [PagingData<MediaGridItem.MediaItem] that can be processed further, or provided to the
* [MediaGrid].
*/
fun Flow<PagingData<Media>>.toMediaGridItemFromMedia(): Flow<PagingData<MediaGridItem.MediaItem>> {
return this.map { pagingData -> pagingData.map { MediaGridItem.MediaItem(it) } }
}
/**
* An extension function to prepare a flow of [PagingData<Album>] to be provided to the [MediaGrid]
* composable, by wrapping all of the [Album] objects in a [MediaGridItem].
*
* @return A [PagingData<MediaGridItem>] that can be processed further, or provided to the
* [MediaGrid].
*/
fun Flow<PagingData<Group.Album>>.toMediaGridItemFromAlbum(): Flow<PagingData<MediaGridItem>> {
return this.map { pagingData -> pagingData.map { MediaGridItem.AlbumItem(it) } }
}
/**
* An extension function which accepts a flow of [PagingData<MediaGridItem.MediaItem] (the actual
* [Media] grid representation wrappers) and processes them inserting month separators in between
* items that have different month.
*
* TODO(b/323830434): Update logic for separators after 4th row when UX finalizes.
* Note: This does not include a separator for the first month of data.
*
* @return A [PagingData<MediaGridItem] that can be processed further, or provided to the
* [MediaGrid].
*/
fun Flow<PagingData<MediaGridItem.MediaItem>>.insertMonthSeparators():
Flow<PagingData<MediaGridItem>> {
return this.map {
it.insertSeparators { before, after ->
// If this is the first or last item in the list, no separators are required.
if (after == null || before == null) {
return@insertSeparators null
}
// ZoneOffset.UTC is used here because all timestamps are expected to be millisecionds
// since epoch in UTC. See [CloudMediaProviderContract#MediaColumns.DATE_TAKEN_MILLIS]
val beforeLocalDateTime =
LocalDateTime.ofEpochSecond((before.media.getTimestamp() / 1000), 0, ZoneOffset.UTC)
val afterLocalDateTime =
LocalDateTime.ofEpochSecond((after.media.getTimestamp() / 1000), 0, ZoneOffset.UTC)
if (beforeLocalDateTime.getMonth() != afterLocalDateTime.getMonth()) {
val format =
// If the current calendar year is different from the items year, append the
// year to to the month string.
if (afterLocalDateTime.getYear() != LocalDateTime.now().getYear()) "MMMM YYYY"
// The year is the same, so just use the month's name.
else "MMMM"
// The months are different, so insert a separator between [before] and [after]
// by returning it here.
MediaGridItem.SeparatorItem(
afterLocalDateTime.format(DateTimeFormatter.ofPattern(format))
)
} else {
// Both Media have the same month, so no separator needed between the two.
null
}
}
}
}