Forráskód Böngészése

Added pagination support for recent photos

Ana Sekuloski 3 éve
szülő
commit
dfddd51d7e

+ 10 - 2
flickersearchlibrary/src/main/java/com/livelike/flickersearchlibrary/Flickr.kt

@@ -16,6 +16,8 @@ object Flickr {
 
     private val flickrService: FlickrService by inject(FlickrService::class.java)
 
+    private const val DEFAULT_NUMBER_OF_PHOTOS_PER_PAGE = 20
+
     /**
      * Provides search results for given search term.
      * If no search query is provided, empty results will be returned.
@@ -26,10 +28,16 @@ object Flickr {
     suspend fun search(searchQuery: String?) = flickrService.search(searchQuery)
 
     /**
-     * Retrieves recently published photos on Flickr.
+     * Retrieves [numOfPhotos] recently published photos on Flickr on the given [page].
+     * By default, 20 photos are returned, and the default page is the first page.
      *
+     * @param numOfPhotos [Int] number of photos to return.
+     * @param page [Int] the page where photos should be contained.
      * @return [List] of recently published photos on Flickr.
      */
-    suspend fun getRecentPhotos() = flickrService.getRecentPhotos()
+    suspend fun getRecentPhotos(
+        numOfPhotos: Int = DEFAULT_NUMBER_OF_PHOTOS_PER_PAGE,
+        page: Int = 1
+    ) = flickrService.getRecentPhotos(numOfPhotos, page)
 
 }

+ 2 - 0
flickersearchlibrary/src/main/java/com/livelike/flickersearchlibrary/api/FlickrApi.kt

@@ -22,6 +22,8 @@ internal interface FlickrApi {
 
     @GET(".")
     suspend fun getRecent(
+        @Query(QUERY_PER_PAGE) perPage: Int,
+        @Query(QUERY_PAGE) page: Int,
         @Query(QUERY_METHOD) method: String = RECENT_IMAGES_METHOD,
         @Query(QUERY_API_KEY) apiKey: String = DEFAULT_API_KEY,
         @Query(QUERY_FORMAT) format: String = JSON_FORMAT,

+ 3 - 1
flickersearchlibrary/src/main/java/com/livelike/flickersearchlibrary/api/utils/Constants.kt

@@ -19,5 +19,7 @@ internal const val QUERY_METHOD = "method"
 internal const val SEARCH_METHOD = "flickr.photos.search"
 internal const val RECENT_IMAGES_METHOD = "flickr.photos.getRecent"
 
-// Constant Query keys for methods supported in the library
+// Constant Query keys for arguments supported in the library
 internal const val QUERY_TEXT = "text"
+internal const val QUERY_PER_PAGE = "per_page"
+internal const val QUERY_PAGE = "page"

+ 5 - 3
flickersearchlibrary/src/main/java/com/livelike/flickersearchlibrary/service/FlickrService.kt

@@ -5,7 +5,7 @@ import com.livelike.flickersearchlibrary.ui.model.PhotoItem
 /**
  * Interface for the service providing Flickr features.
  */
-interface FlickrService {
+internal interface FlickrService {
 
     /**
      * Searches for photos and retrieves [List] of [PhotoItem]s.
@@ -17,9 +17,11 @@ interface FlickrService {
     suspend fun search(searchQuery: String?): List<PhotoItem>
 
     /**
-     * Retrieves recently published photos on Flickr.
+     * Retrieves [numOfPhotos] recently published photos on Flickr on the given [page].
      *
+     * @param numOfPhotos [Int] number of photos to return.
+     * @param page [Int] the page where the photos should be contained.
      * @return [List] of recently published photos on Flickr.
      */
-    suspend fun getRecentPhotos(): List<PhotoItem>
+    suspend fun getRecentPhotos(numOfPhotos: Int, page: Int): List<PhotoItem>
 }

+ 2 - 2
flickersearchlibrary/src/main/java/com/livelike/flickersearchlibrary/service/logic/FlickrServiceLogic.kt

@@ -29,9 +29,9 @@ internal class FlickrServiceLogic(private val api: FlickrApi) : FlickrService {
             cachedPhotos
         } ?: emptyList()
 
-    override suspend fun getRecentPhotos(): List<PhotoItem> {
+    override suspend fun getRecentPhotos(numOfPhotos: Int, page: Int): List<PhotoItem> {
         val response = executeApiCall {
-            api.getRecent()
+            api.getRecent(numOfPhotos, page)
         }
         return when (response) {
             is ApiResponse.Success -> response.body.map { it.toPhotoItem() }

+ 12 - 1
flickersearchlibrary/src/main/java/com/livelike/flickersearchlibrary/ui/FlickrPhotosActivity.kt

@@ -6,6 +6,7 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.widget.SearchView
 import androidx.lifecycle.lifecycleScope
 import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
 import com.livelike.flickersearchlibrary.databinding.ActivityFlickrPhotosBinding
 import com.livelike.flickersearchlibrary.ui.adapter.FlickrPhotosAdapter
 
@@ -20,6 +21,7 @@ class FlickrPhotosActivity : AppCompatActivity() {
 
     private companion object {
         private const val PHOTOS_DEFAULT_COLUMN_COUNT = 3
+        private const val BOTTOM_DIRECTION = 1
     }
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -57,11 +59,20 @@ class FlickrPhotosActivity : AppCompatActivity() {
                 return true
             }
         })
+
+        views.gridView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
+            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+                super.onScrolled(recyclerView, dx, dy)
+                if (!recyclerView.canScrollVertically(BOTTOM_DIRECTION)) {
+                    viewModel.loadMorePhotos()
+                }
+            }
+        })
     }
 
     private fun setupObservers() {
         viewModel.photos.observe(this) {
-            photosAdapter.setItems(it)
+            photosAdapter.appendItems(it)
         }
     }
 

+ 19 - 0
flickersearchlibrary/src/main/java/com/livelike/flickersearchlibrary/ui/FlickrPhotosViewModel.kt

@@ -13,11 +13,30 @@ internal class FlickrPhotosViewModel : ViewModel() {
     private val _photos = MutableLiveData<List<PhotoItem>>()
     val photos: LiveData<List<PhotoItem>> = _photos
 
+    private lateinit var visiblePhotosState: PhotosState
+
     fun getRecentPhotos() = viewModelScope.launch {
         _photos.postValue(Flickr.getRecentPhotos())
+        visiblePhotosState = PhotosState(PhotoType.RECENT, 1)
     }
 
     fun searchPhotos(searchQuery: String?) = viewModelScope.launch {
         _photos.postValue(Flickr.search(searchQuery))
+        visiblePhotosState = PhotosState(PhotoType.SEARCH, 1)
+    }
+
+    fun loadMorePhotos() = viewModelScope.launch {
+        val nextPage = visiblePhotosState.currentPage + 1
+        if (visiblePhotosState.type == PhotoType.RECENT) {
+            _photos.postValue(Flickr.getRecentPhotos(page = nextPage))
+        }
+        visiblePhotosState = visiblePhotosState.copy(currentPage = nextPage)
+    }
+
+    private data class PhotosState(var type: PhotoType, var currentPage: Int)
+
+    private enum class PhotoType {
+        RECENT,
+        SEARCH
     }
 }

+ 4 - 2
flickersearchlibrary/src/main/java/com/livelike/flickersearchlibrary/ui/adapter/FlickrPhotosAdapter.kt

@@ -12,8 +12,10 @@ class FlickrPhotosAdapter : RecyclerView.Adapter<FlickrPhotosAdapter.PhotoViewHo
 
     private val itemsListDiffer = AsyncListDiffer(this, PhotoItemsDiffCallback())
 
-    fun setItems(items: List<PhotoItem>) {
-        itemsListDiffer.submitList(items)
+    fun appendItems(items: List<PhotoItem>) {
+        val currentList = itemsListDiffer.currentList.toMutableList()
+        currentList.addAll(items)
+        itemsListDiffer.submitList(currentList)
     }
 
     override fun onCreateViewHolder(

+ 2 - 3
flickersearchlibrary/src/main/res/layout/activity_flickr_photos.xml

@@ -10,18 +10,17 @@
         android:id="@+id/searchView"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginVertical="16dp"
         app:iconifiedByDefault="false"
+        app:layout_constraintBottom_toTopOf="@id/gridView"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/gridView"
         app:queryHint="@string/search_photos_text" />
 
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/gridView"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
+        android:layout_height="0dp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/searchView" />