Explorar o código

Rewrite catalogue adapter

len %!s(int64=8) %!d(string=hai) anos
pai
achega
871e17c2f5

+ 0 - 107
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueAdapter.kt

@@ -1,107 +0,0 @@
-package eu.kanade.tachiyomi.ui.catalogue
-
-import android.view.Gravity
-import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.widget.FrameLayout
-import eu.davidea.flexibleadapter4.FlexibleAdapter
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.database.models.Manga
-import eu.kanade.tachiyomi.util.inflate
-import eu.kanade.tachiyomi.widget.AutofitRecyclerView
-import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
-import java.util.*
-
-/**
- * Adapter storing a list of manga from the catalogue.
- *
- * @param fragment the fragment containing this adapter.
- */
-class CatalogueAdapter(val fragment: CatalogueFragment) : FlexibleAdapter<CatalogueHolder, Manga>() {
-
-    /**
-     * Property to get the list of manga in the adapter.
-     */
-    val items: List<Manga>
-        get() = mItems
-
-    init {
-        mItems = ArrayList()
-        setHasStableIds(true)
-    }
-
-    /**
-     * Adds a list of manga to the adapter.
-     *
-     * @param list the list to add.
-     */
-    fun addItems(list: List<Manga>) {
-        if (list.isNotEmpty()) {
-            val sizeBeforeAdding = mItems.size
-            mItems.addAll(list)
-            notifyItemRangeInserted(sizeBeforeAdding, list.size)
-        }
-    }
-
-    /**
-     * Clears the list of manga from the adapter.
-     */
-    fun clear() {
-        val sizeBeforeRemoving = mItems.size
-        mItems.clear()
-        notifyItemRangeRemoved(0, sizeBeforeRemoving)
-    }
-
-    /**
-     * Returns the identifier for a manga.
-     *
-     * @param position the position in the adapter.
-     * @return an identifier for the item.
-     */
-    override fun getItemId(position: Int): Long {
-        return mItems[position].id!!
-    }
-
-    /**
-     * Used to filter the list. Required but not used.
-     */
-    override fun updateDataSet(param: String) {}
-
-    /**
-     * Creates a new view holder.
-     *
-     * @param parent the parent view.
-     * @param viewType the type of the holder.
-     * @return a new view holder for a manga.
-     */
-    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CatalogueHolder {
-        if (parent.id == R.id.catalogue_grid) {
-            val view = parent.inflate(R.layout.item_catalogue_grid).apply {
-                card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight)
-                gradient.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM)
-            }
-            return CatalogueGridHolder(view, this, fragment)
-        } else {
-            val view = parent.inflate(R.layout.item_catalogue_list)
-            return CatalogueListHolder(view, this, fragment)
-        }
-    }
-
-    /**
-     * Binds a holder with a new position.
-     *
-     * @param holder the holder to bind.
-     * @param position the position to bind.
-     */
-    override fun onBindViewHolder(holder: CatalogueHolder, position: Int) {
-        val manga = getItem(position)
-        holder.onSetValues(manga)
-    }
-
-    /**
-     * Property to return the height for the covers based on the width to keep an aspect ratio.
-     */
-    val coverHeight: Int
-        get() = (fragment.recycler as AutofitRecyclerView).itemWidth / 3 * 4
-
-}

+ 65 - 42
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt

@@ -11,11 +11,12 @@ import android.widget.ProgressBar
 import android.widget.Spinner
 import com.afollestad.materialdialogs.MaterialDialog
 import com.f2prateek.rx.preferences.Preference
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.davidea.flexibleadapter.items.IFlexible
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.source.model.FilterList
 import eu.kanade.tachiyomi.data.source.online.LoginSource
-import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
 import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
 import eu.kanade.tachiyomi.ui.main.MainActivity
 import eu.kanade.tachiyomi.ui.manga.MangaActivity
@@ -24,7 +25,6 @@ import eu.kanade.tachiyomi.util.inflate
 import eu.kanade.tachiyomi.util.snack
 import eu.kanade.tachiyomi.util.toast
 import eu.kanade.tachiyomi.widget.AutofitRecyclerView
-import eu.kanade.tachiyomi.widget.EndlessScrollListener
 import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
 import kotlinx.android.synthetic.main.activity_main.*
 import kotlinx.android.synthetic.main.fragment_catalogue.*
@@ -40,7 +40,10 @@ import java.util.concurrent.TimeUnit.MILLISECONDS
  * Uses R.layout.fragment_catalogue.
  */
 @RequiresPresenter(CataloguePresenter::class)
-open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHolder.OnListItemClickListener {
+open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(),
+        FlexibleAdapter.OnItemClickListener,
+        FlexibleAdapter.OnItemLongClickListener,
+        FlexibleAdapter.EndlessScrollListener<ProgressItem> {
 
     /**
      * Spinner shown in the toolbar to change the selected source.
@@ -50,12 +53,7 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
     /**
      * Adapter containing the list of manga from the catalogue.
      */
-    private lateinit var adapter: CatalogueAdapter
-
-    /**
-     * Scroll listener. It loads next pages when the end of the list is reached.
-     */
-    private var scrollListener: EndlessScrollListener? = null
+    private lateinit var adapter: FlexibleAdapter<IFlexible<*>>
 
     /**
      * Query of the search box.
@@ -130,6 +128,8 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
 
     lateinit var recycler: RecyclerView
 
+    private var progressItem: ProgressItem? = null
+
     companion object {
         /**
          * Creates a new instance of this fragment.
@@ -160,7 +160,7 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
         }
 
         // Initialize adapter, scroll listener and recycler views
-        adapter = CatalogueAdapter(this)
+        adapter = FlexibleAdapter(null, this)
         setupRecycler()
 
         // Create toolbar spinner
@@ -251,11 +251,18 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
                         .skip(1)
                         // Set again the adapter to recalculate the covers height
                         .subscribe { adapter = [email protected] }
+
+                (layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
+                    override fun getSpanSize(position: Int): Int {
+                        return when (adapter.getItemViewType(position)) {
+                            R.layout.item_catalogue_grid -> 1
+                            else -> spanCount
+                        }
+                    }
+                }
             }
         }
-        scrollListener = EndlessScrollListener(recycler.layoutManager as LinearLayoutManager, { requestNextPage() })
         recycler.setHasFixedSize(true)
-        recycler.addOnScrollListener(scrollListener)
         recycler.adapter = adapter
 
         catalogue_view.addView(recycler, 1)
@@ -376,29 +383,19 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
         presenter.restartPager(newQuery)
     }
 
-    /**
-     * Requests the next page (if available). Called from scroll listeners when they reach the end.
-     */
-    private fun requestNextPage() {
-        if (presenter.hasNextPage()) {
-            showGridProgressBar()
-            presenter.requestNext()
-        }
-    }
-
     /**
      * Called from the presenter when the network request is received.
      *
      * @param page the current page.
      * @param mangas the list of manga of the page.
      */
-    fun onAddPage(page: Int, mangas: List<Manga>) {
+    fun onAddPage(page: Int, mangas: List<CatalogueItem>) {
         hideProgressBar()
         if (page == 1) {
             adapter.clear()
-            scrollListener?.resetScroll()
+            resetProgressItem()
         }
-        adapter.addItems(mangas)
+        adapter.onLoadMoreComplete(mangas)
     }
 
     /**
@@ -407,6 +404,7 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
      * @param error the error received.
      */
     fun onAddPageError(error: Throwable) {
+        adapter.onLoadMoreComplete(null)
         hideProgressBar()
 
         val message = if (error is NoResultsException) "No results found" else (error.message ?: "")
@@ -414,12 +412,42 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
         snack?.dismiss()
         snack = catalogue_view.snack(message, Snackbar.LENGTH_INDEFINITE) {
             setAction(R.string.action_retry) {
-                showProgressBar()
+                // If not the first page, show bottom progress bar.
+                if (adapter.mainItemCount > 0) {
+                    val item = progressItem ?: return@setAction
+                    adapter.addScrollableFooterWithDelay(item, 0, true)
+                } else {
+                    showProgressBar()
+                }
                 presenter.requestNext()
             }
         }
     }
 
+    /**
+     * Sets a new progress item and reenables the scroll listener.
+     */
+    private fun resetProgressItem() {
+        progressItem = ProgressItem()
+        adapter.endlessTargetCount = 0
+        adapter.setEndlessScrollListener(this, progressItem!!)
+    }
+
+    /**
+     * Called by the adapter when scrolled near the bottom.
+     */
+    override fun onLoadMore(lastPosition: Int, currentPage: Int) {
+        if (presenter.hasNextPage()) {
+            presenter.requestNext()
+        } else {
+            adapter.onLoadMoreComplete(null)
+            adapter.endlessTargetCount = 1
+        }
+    }
+
+    override fun noMoreLoad(newItemsSize: Int) {
+    }
+
     /**
      * Called from the presenter when a manga is initialized.
      *
@@ -433,13 +461,18 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
      * Swaps the current display mode.
      */
     fun swapDisplayMode() {
+        if (!isAdded) return
+
         presenter.swapDisplayMode()
         val isListMode = presenter.isListMode
         activity.invalidateOptionsMenu()
         setupRecycler()
         if (!isListMode || !context.connectivityManager.isActiveNetworkMetered) {
             // Initialize mangas if going to grid view or if over wifi when going to list view
-            presenter.initializeMangas(adapter.items)
+            val mangas = (0..adapter.itemCount-1).mapNotNull {
+                (adapter.getItem(it) as? CatalogueItem)?.manga
+            }
+            presenter.initializeMangas(mangas)
         }
     }
 
@@ -474,21 +507,11 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
         snack = null
     }
 
-    /**
-     * Shows the progress bar at the end of the screen.
-     */
-    private fun showGridProgressBar() {
-        progress_grid.visibility = ProgressBar.VISIBLE
-        snack?.dismiss()
-        snack = null
-    }
-
     /**
      * Hides active progress bars.
      */
     private fun hideProgressBar() {
         progress.visibility = ProgressBar.GONE
-        progress_grid.visibility = ProgressBar.GONE
     }
 
     /**
@@ -497,10 +520,10 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
      * @param position the position of the element clicked.
      * @return true if the item should be selected, false otherwise.
      */
-    override fun onListItemClick(position: Int): Boolean {
-        val item = adapter.getItem(position) ?: return false
+    override fun onItemClick(position: Int): Boolean {
+        val item = adapter.getItem(position) as? CatalogueItem ?: return false
 
-        val intent = MangaActivity.newIntent(activity, item, true)
+        val intent = MangaActivity.newIntent(activity, item.manga, true)
         startActivity(intent)
         return false
     }
@@ -510,8 +533,8 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleVie
      *
      * @param position the position of the element clicked.
      */
-    override fun onListItemLongClick(position: Int) {
-        val manga = adapter.getItem(position) ?: return
+    override fun onItemLongClick(position: Int) {
+        val manga = (adapter.getItem(position) as? CatalogueItem?)?.manga ?: return
 
         val textRes = if (manga.favorite) R.string.remove_from_library else R.string.add_to_library
 

+ 3 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueGridHolder.kt

@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.catalogue
 import android.view.View
 import com.bumptech.glide.Glide
 import com.bumptech.glide.load.engine.DiskCacheStrategy
+import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.kanade.tachiyomi.data.database.models.Manga
 import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
 
@@ -12,11 +13,10 @@ import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
  *
  * @param view the inflated view for this holder.
  * @param adapter the adapter handling this holder.
- * @param listener a listener to react to single tap and long tap events.
  * @constructor creates a new catalogue holder.
  */
-class CatalogueGridHolder(private val view: View, private val adapter: CatalogueAdapter, listener: OnListItemClickListener) :
-        CatalogueHolder(view, adapter, listener) {
+class CatalogueGridHolder(private val view: View, private val adapter: FlexibleAdapter<*>) :
+        CatalogueHolder(view, adapter) {
 
     /**
      * Method called from [CatalogueAdapter.onBindViewHolder]. It updates the data for this

+ 4 - 4
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueHolder.kt

@@ -1,18 +1,18 @@
 package eu.kanade.tachiyomi.ui.catalogue
 
 import android.view.View
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.davidea.viewholders.FlexibleViewHolder
 import eu.kanade.tachiyomi.data.database.models.Manga
-import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
 
 /**
  * Generic class used to hold the displayed data of a manga in the catalogue.
  *
  * @param view the inflated view for this holder.
  * @param adapter the adapter handling this holder.
- * @param listener a listener to react to single tap and long tap events.
  */
-abstract class CatalogueHolder(view: View, adapter: CatalogueAdapter, listener: OnListItemClickListener) :
-        FlexibleViewHolder(view, adapter, listener) {
+abstract class CatalogueHolder(view: View, adapter: FlexibleAdapter<*>) :
+        FlexibleViewHolder(view, adapter) {
 
     /**
      * Method called from [CatalogueAdapter.onBindViewHolder]. It updates the data for this

+ 53 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueItem.kt

@@ -0,0 +1,53 @@
+package eu.kanade.tachiyomi.ui.catalogue
+
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
+import eu.davidea.flexibleadapter.items.IFlexible
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.util.inflate
+import eu.kanade.tachiyomi.widget.AutofitRecyclerView
+import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
+
+class CatalogueItem(val manga: Manga) : AbstractFlexibleItem<CatalogueHolder>() {
+
+    override fun getLayoutRes(): Int {
+        return R.layout.item_catalogue_grid
+    }
+
+    override fun createViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, inflater: LayoutInflater, parent: ViewGroup): CatalogueHolder {
+        if (parent is AutofitRecyclerView) {
+            val view = parent.inflate(R.layout.item_catalogue_grid).apply {
+                card.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.itemWidth / 3 * 4)
+                gradient.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.itemWidth / 3 * 4 / 2, Gravity.BOTTOM)
+            }
+            return CatalogueGridHolder(view, adapter)
+        } else {
+            val view = parent.inflate(R.layout.item_catalogue_list)
+            return CatalogueListHolder(view, adapter)
+        }
+    }
+
+    override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: CatalogueHolder, position: Int, payloads: List<Any?>?) {
+        holder.onSetValues(manga)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other is CatalogueItem) {
+            return manga.id!! == other.manga.id!!
+        }
+        return false
+    }
+
+    override fun hashCode(): Int {
+        return manga.id!!.hashCode()
+    }
+
+
+
+}

+ 3 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueListHolder.kt

@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.catalogue
 import android.view.View
 import com.bumptech.glide.Glide
 import com.bumptech.glide.load.engine.DiskCacheStrategy
+import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.util.getResourceColor
 import kotlinx.android.synthetic.main.item_catalogue_list.view.*
@@ -13,11 +14,10 @@ import kotlinx.android.synthetic.main.item_catalogue_list.view.*
  *
  * @param view the inflated view for this holder.
  * @param adapter the adapter handling this holder.
- * @param listener a listener to react to single tap and long tap events.
  * @constructor creates a new catalogue holder.
  */
-class CatalogueListHolder(private val view: View, adapter: CatalogueAdapter, listener: OnListItemClickListener) :
-        CatalogueHolder(view, adapter, listener) {
+class CatalogueListHolder(private val view: View, adapter: FlexibleAdapter<*>) :
+        CatalogueHolder(view, adapter) {
 
     private val favoriteColor = view.context.getResourceColor(android.R.attr.textColorHint)
     private val unfavoriteColor = view.context.getResourceColor(android.R.attr.textColorPrimary)

+ 1 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt

@@ -163,6 +163,7 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
                 .observeOn(Schedulers.io())
                 .map { it.first to it.second.map { networkToLocalManga(it, sourceId) } }
                 .doOnNext { initializeMangas(it.second) }
+                .map { it.first to it.second.map(::CatalogueItem) }
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribeReplay({ view, pair ->
                     view.onAddPage(pair.first, pair.second)

+ 52 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/ProgressItem.kt

@@ -0,0 +1,52 @@
+package eu.kanade.tachiyomi.ui.catalogue
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ProgressBar
+import android.widget.TextView
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
+import eu.davidea.flexibleadapter.items.IFlexible
+import eu.davidea.viewholders.FlexibleViewHolder
+import eu.kanade.tachiyomi.R
+
+
+class ProgressItem : AbstractFlexibleItem<ProgressItem.Holder>() {
+
+    var loadMore = true
+
+    override fun getLayoutRes(): Int {
+        return R.layout.progress_item
+    }
+
+    override fun createViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, inflater: LayoutInflater, parent: ViewGroup): Holder {
+        return Holder(inflater.inflate(layoutRes, parent, false), adapter)
+    }
+
+    override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<*>>, holder: Holder, position: Int, payloads: List<Any?>) {
+        holder.progressBar.visibility = View.GONE
+        holder.progressMessage.visibility = View.GONE
+
+        if (!adapter.isEndlessScrollEnabled) {
+            loadMore = false
+        }
+
+        if (loadMore) {
+            holder.progressBar.visibility = View.VISIBLE
+        } else {
+            holder.progressMessage.visibility = View.VISIBLE
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return this === other
+    }
+
+    class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
+
+        val progressBar = view.findViewById(R.id.progress_bar) as ProgressBar
+        val progressMessage = view.findViewById(R.id.progress_message) as TextView
+    }
+
+}

+ 0 - 46
app/src/main/java/eu/kanade/tachiyomi/widget/EndlessScrollListener.kt

@@ -1,46 +0,0 @@
-package eu.kanade.tachiyomi.widget
-
-import android.support.v7.widget.LinearLayoutManager
-import android.support.v7.widget.RecyclerView
-
-class EndlessScrollListener(
-        private val layoutManager: LinearLayoutManager,
-        private val requestNext: () -> Unit)
-: RecyclerView.OnScrollListener() {
-
-    companion object {
-        // The minimum amount of items to have below your current scroll position before loading
-        // more.
-        private val VISIBLE_THRESHOLD = 5
-    }
-
-    private var previousTotal = 0 // The total number of items in the dataset after the last load
-    private var loading = true // True if we are still waiting for the last set of data to load.
-    private var firstVisibleItem = 0
-    private var visibleItemCount = 0
-    private var totalItemCount = 0
-
-    fun resetScroll() {
-        previousTotal = 0
-        loading = true
-    }
-
-    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
-        super.onScrolled(recyclerView, dx, dy)
-
-        visibleItemCount = recyclerView.childCount
-        totalItemCount = layoutManager.itemCount
-        firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
-
-        if (loading && totalItemCount > previousTotal) {
-            loading = false
-            previousTotal = totalItemCount
-        }
-        if (!loading && totalItemCount - visibleItemCount <= firstVisibleItem + VISIBLE_THRESHOLD) {
-            // End has been reached
-            requestNext()
-            loading = true
-        }
-    }
-
-}

+ 0 - 8
app/src/main/res/layout/fragment_catalogue.xml

@@ -21,14 +21,6 @@
             android:layout_gravity="center_vertical|center_horizontal"
             android:visibility="gone"/>
 
-        <ProgressBar
-            android:id="@+id/progress_grid"
-            style="?android:attr/progressBarStyle"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical|center_horizontal"
-            android:visibility="gone"/>
-
     </LinearLayout>
 
 </android.support.design.widget.CoordinatorLayout>

+ 25 - 0
app/src/main/res/layout/progress_item.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:padding="8dp">
+
+    <ProgressBar
+        android:id="@+id/progress_bar"
+        style="@style/Widget.AppCompat.ProgressBar"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        android:layout_gravity="center"/>
+
+    <TextView
+        android:id="@+id/progress_message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/no_more_results"
+        android:layout_gravity="center"
+        android:visibility="gone"
+        tools:visibility="visible"/>
+
+</FrameLayout>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -223,6 +223,7 @@
     <string name="source_requires_login">This source requires you to log in</string>
     <string name="select_source">Select a source</string>
     <string name="no_valid_sources">Please enable at least one valid source</string>
+    <string name="no_more_results">No more results</string>
 
     <!-- Manga activity -->
     <string name="manga_not_in_db">This manga was removed from the database!</string>