1 /*
<lambda>null2 * Copyright 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.photopicker.extensions
18
19 import androidx.paging.PagingData
20 import androidx.paging.insertSeparators
21 import androidx.paging.map
22 import com.android.photopicker.core.components.MediaGridItem
23 import com.android.photopicker.data.model.Group
24 import com.android.photopicker.data.model.Media
25 import java.time.LocalDateTime
26 import java.time.ZoneOffset
27 import java.time.format.DateTimeFormatter
28 import kotlinx.coroutines.flow.Flow
29 import kotlinx.coroutines.flow.map
30
31 /**
32 * An extension function to prepare a flow of [PagingData<Media>] to be provided to the [MediaGrid]
33 * composable, by wrapping all of the [Media] objects in a [MediaGridItem.MediaItem].
34 *
35 * @return A [PagingData<MediaGridItem.MediaItem] that can be processed further, or provided to the
36 * [MediaGrid].
37 */
38 fun Flow<PagingData<Media>>.toMediaGridItemFromMedia(): Flow<PagingData<MediaGridItem.MediaItem>> {
39 return this.map { pagingData -> pagingData.map { MediaGridItem.MediaItem(it) } }
40 }
41
42 /**
43 * An extension function to prepare a flow of [PagingData<Album>] to be provided to the [MediaGrid]
44 * composable, by wrapping all of the [Album] objects in a [MediaGridItem].
45 *
46 * @return A [PagingData<MediaGridItem>] that can be processed further, or provided to the
47 * [MediaGrid].
48 */
toMediaGridItemFromAlbumnull49 fun Flow<PagingData<Group.Album>>.toMediaGridItemFromAlbum(): Flow<PagingData<MediaGridItem>> {
50 return this.map { pagingData -> pagingData.map { MediaGridItem.AlbumItem(it) } }
51 }
52
53 /**
54 * An extension function which accepts a flow of [PagingData<MediaGridItem.MediaItem] (the actual
55 * [Media] grid representation wrappers) and processes them inserting month separators in between
56 * items that have different month.
57 *
58 * TODO(b/323830434): Update logic for separators after 4th row when UX finalizes.
59 * Note: This does not include a separator for the first month of data.
60 *
61 * @return A [PagingData<MediaGridItem] that can be processed further, or provided to the
62 * [MediaGrid].
63 */
insertMonthSeparatorsnull64 fun Flow<PagingData<MediaGridItem.MediaItem>>.insertMonthSeparators():
65 Flow<PagingData<MediaGridItem>> {
66 return this.map {
67 it.insertSeparators { before, after ->
68
69 // If this is the first or last item in the list, no separators are required.
70 if (after == null || before == null) {
71 return@insertSeparators null
72 }
73
74 // ZoneOffset.UTC is used here because all timestamps are expected to be millisecionds
75 // since epoch in UTC. See [CloudMediaProviderContract#MediaColumns.DATE_TAKEN_MILLIS]
76 val beforeLocalDateTime =
77 LocalDateTime.ofEpochSecond((before.media.getTimestamp() / 1000), 0, ZoneOffset.UTC)
78 val afterLocalDateTime =
79 LocalDateTime.ofEpochSecond((after.media.getTimestamp() / 1000), 0, ZoneOffset.UTC)
80
81 if (beforeLocalDateTime.getMonth() != afterLocalDateTime.getMonth()) {
82 val format =
83 // If the current calendar year is different from the items year, append the
84 // year to to the month string.
85 if (afterLocalDateTime.getYear() != LocalDateTime.now().getYear()) "MMMM YYYY"
86
87 // The year is the same, so just use the month's name.
88 else "MMMM"
89
90 // The months are different, so insert a separator between [before] and [after]
91 // by returning it here.
92 MediaGridItem.SeparatorItem(
93 afterLocalDateTime.format(DateTimeFormatter.ofPattern(format))
94 )
95 } else {
96 // Both Media have the same month, so no separator needed between the two.
97 null
98 }
99 }
100 }
101 }
102