1 /* <lambda>null2 * Copyright (C) 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.data.paging 18 19 import android.net.Uri 20 import androidx.paging.PagingSource 21 import androidx.paging.PagingSource.LoadParams 22 import androidx.paging.PagingSource.LoadResult 23 import androidx.paging.PagingState 24 import com.android.photopicker.data.model.Group 25 import com.android.photopicker.data.model.MediaPageKey 26 import com.android.photopicker.data.model.MediaSource 27 import java.time.LocalDateTime 28 import java.time.ZoneOffset 29 import java.time.temporal.ChronoUnit 30 31 /** 32 * This [FakeInMemoryAlbumPagingSource] class is responsible to providing paginated album data from 33 * Picker Database by serving requests from Paging library. 34 * 35 * It generates and returns its own fake data. 36 */ 37 class FakeInMemoryAlbumPagingSource 38 private constructor( 39 val DATA_SIZE: Int = DEFAULT_SIZE, 40 private val DATA_LIST: List<Group.Album>? = null, 41 ) : PagingSource<MediaPageKey, Group.Album>() { 42 43 companion object { 44 const val TEST_ALBUM_NAME_PREFIX = "AlbumNumber_" 45 const val DEFAULT_SIZE = 1_000 46 } 47 48 constructor(dataSize: Int = DEFAULT_SIZE) : this(dataSize, null) 49 50 constructor(dataList: List<Group.Album>) : this(DEFAULT_SIZE, dataList) 51 52 private val currentDateTime = LocalDateTime.now() 53 54 // If a [DATA_LIST] was provided, use it, otherwise generate a list of the requested size. 55 val DATA = 56 DATA_LIST 57 ?: buildList<Group.Album> { 58 for (i in 1..DATA_SIZE) { 59 add( 60 Group.Album( 61 id = "$i", 62 pickerId = i.toLong(), 63 authority = "a", 64 displayName = TEST_ALBUM_NAME_PREFIX + "$i", 65 coverUri = 66 Uri.EMPTY.buildUpon() 67 .apply { 68 scheme("content") 69 authority("a") 70 path("$i") 71 } 72 .build(), 73 dateTakenMillisLong = 74 currentDateTime 75 .minus(i.toLong(), ChronoUnit.DAYS) 76 .toEpochSecond(ZoneOffset.UTC) * 1000, 77 coverMediaSource = MediaSource.LOCAL, 78 ) 79 ) 80 } 81 } 82 83 override suspend fun load( 84 params: LoadParams<MediaPageKey> 85 ): LoadResult<MediaPageKey, Group.Album> { 86 87 // Handle a data size of 0 for the first page, and return an empty page with no further 88 // keys. 89 if (DATA.size == 0 && params.key == null) { 90 return LoadResult.Page(data = emptyList(), nextKey = null, prevKey = null) 91 } 92 93 // This is inefficient, but a reliable way to locate the record being requested by the 94 // [MediaPageKey] without having to keep track of offsets. 95 val startIndex = 96 if (params.key == null) { 97 0 98 } else { 99 DATA.indexOfFirst({ item -> item.pickerId == params.key?.pickerId ?: 1 }) 100 } 101 102 // The list is zero-based, and loadSize isn't; so, offset by 1 103 val endIndex = Math.min((startIndex + params.loadSize) - 1, DATA.lastIndex) 104 105 // Item at start position doesn't exist, so this isn't a valid page. 106 if (DATA.getOrNull(startIndex) == null) { 107 return LoadResult.Invalid() 108 } 109 110 val pageData = DATA.slice(startIndex..endIndex) 111 112 // Find the start of the next page and generate a Page key. 113 val nextRow = DATA.getOrNull(endIndex + 1) 114 val nextKey = 115 if (nextRow == null) { 116 null 117 } else { 118 MediaPageKey( 119 pickerId = nextRow.pickerId, 120 dateTakenMillis = nextRow.dateTakenMillisLong, 121 ) 122 } 123 124 // Find the start of the previous page and generate a Page key. 125 val prevPageRow = DATA.getOrNull((startIndex) - params.loadSize) 126 val prevKey = 127 if (prevPageRow == null) { 128 null 129 } else { 130 MediaPageKey( 131 pickerId = prevPageRow.pickerId, 132 dateTakenMillis = prevPageRow.dateTakenMillisLong, 133 ) 134 } 135 136 return LoadResult.Page(data = pageData, nextKey = nextKey, prevKey = prevKey) 137 } 138 139 override fun getRefreshKey(state: PagingState<MediaPageKey, Group.Album>): MediaPageKey? { 140 return state.anchorPosition?.let { null } 141 } 142 } 143