ソースを参照

Added option to sort library (#536)

* Initial code

* Added all sort options

* Fixes

* Removed sort by added. Some renaming

* Removed date added database calls

* Fixes
Bram van de Kerkhof 8 年 前
コミット
aba528b227

+ 4 - 0
app/src/main/java/eu/kanade/tachiyomi/Constants.kt

@@ -6,4 +6,8 @@ object Constants {
     const val NOTIFICATION_DOWNLOAD_CHAPTER_ID = 3
     const val NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID = 4
     const val NOTIFICATION_DOWNLOAD_IMAGE_ID = 5
+
+    const val SORT_LIBRARY_ALPHA = 0
+    const val SORT_LIBRARY_LAST_READ = 1
+    const val SORT_LIBRARY_LAST_UPDATED = 2
 }

+ 9 - 0
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt

@@ -40,6 +40,15 @@ interface HistoryQueries : DbProvider {
                     .build())
             .prepare()
 
+    fun getLastHistoryByMangaId(mangaId: Long) = db.get()
+            .`object`(History::class.java)
+            .withQuery(RawQuery.builder()
+                    .query(getLastHistoryByMangaId())
+                    .args(mangaId)
+                    .observesTables(HistoryTable.TABLE)
+                    .build())
+            .prepare()
+
 
     /**
      * Updates the history last read.

+ 7 - 1
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt

@@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.data.database.DbProvider
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
 import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
+import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
 import eu.kanade.tachiyomi.data.database.tables.ChapterTable
 import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
 import eu.kanade.tachiyomi.data.database.tables.MangaTable
@@ -29,7 +30,7 @@ interface MangaQueries : DbProvider {
             .withGetResolver(LibraryMangaGetResolver.INSTANCE)
             .prepare()
 
-    open fun getFavoriteMangas() = db.get()
+    fun getFavoriteMangas() = db.get()
             .listOfObjects(Manga::class.java)
             .withQuery(Query.builder()
                     .table(MangaTable.TABLE)
@@ -66,6 +67,11 @@ interface MangaQueries : DbProvider {
             .withPutResolver(MangaFlagsPutResolver())
             .prepare()
 
+    fun updateLastUpdated(manga: Manga) = db.put()
+            .`object`(manga)
+            .withPutResolver(MangaLastUpdatedPutResolver())
+            .prepare()
+
     fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
 
     fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()

+ 13 - 0
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt

@@ -73,6 +73,19 @@ fun getHistoryByMangaId() = """
     WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
 """
 
+fun getLastHistoryByMangaId() = """
+    SELECT ${History.TABLE}.*
+    FROM ${History.TABLE}
+    JOIN ${Chapter.TABLE}
+    ON ${History.TABLE}.${History.COL_CHAPTER_ID} = ${Chapter.TABLE}.${Chapter.COL_ID}
+     LEFT JOIN (
+            SELECT MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max
+            FROM ${History.TABLE}
+            GROUP BY ${History.COL_LAST_READ}
+        ) AS M
+    WHERE ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ? AND M.max = ${History.TABLE}.${History.COL_LAST_READ}
+"""
+
 /**
  * Query to get the categories for a manga.
  */

+ 33 - 0
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaLastUpdatedPutResolver.kt

@@ -0,0 +1,33 @@
+package eu.kanade.tachiyomi.data.database.resolvers
+
+import android.content.ContentValues
+import com.pushtorefresh.storio.sqlite.StorIOSQLite
+import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
+import com.pushtorefresh.storio.sqlite.operations.put.PutResult
+import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
+import eu.kanade.tachiyomi.data.database.inTransactionReturn
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.data.database.tables.MangaTable
+
+class MangaLastUpdatedPutResolver : PutResolver<Manga>() {
+
+    override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
+        val updateQuery = mapToUpdateQuery(manga)
+        val contentValues = mapToContentValues(manga)
+
+        val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
+        PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
+    }
+
+    fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
+            .table(MangaTable.TABLE)
+            .where("${MangaTable.COL_ID} = ?")
+            .whereArgs(manga.id)
+            .build()
+
+    fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
+        put(MangaTable.COL_LAST_UPDATE, manga.last_update)
+    }
+
+}
+

+ 7 - 1
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt

@@ -251,7 +251,13 @@ class LibraryUpdateService : Service() {
                             .map { manga }
                 }
                 // Add manga with new chapters to the list.
-                .doOnNext { newUpdates.add(it) }
+                .doOnNext { manga ->
+                    // Set last updated time
+                    manga.last_update = Date().time
+                    db.updateLastUpdated(manga).executeAsBlocking()
+                    // Add to the list
+                    newUpdates.add(manga)
+                }
                 // Notify result of the overall update.
                 .doOnCompleted {
                     if (newUpdates.isEmpty()) {

+ 2 - 0
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt

@@ -83,6 +83,8 @@ class PreferenceKeys(context: Context) {
 
     val filterUnread = context.getString(R.string.pref_filter_unread_key)
 
+    val librarySortingMode = context.getString(R.string.pref_library_sorting_mode_key)
+
     val automaticUpdates = context.getString(R.string.pref_enable_automatic_updates_key)
 
     val startScreen = context.getString(R.string.pref_start_screen_key)

+ 2 - 0
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -126,6 +126,8 @@ class PreferencesHelper(context: Context) {
 
     fun filterUnread() = rxPrefs.getBoolean(keys.filterUnread, false)
 
+    fun librarySortingMode() = rxPrefs.getInteger(keys.librarySortingMode, 0)
+
     fun automaticUpdates() = prefs.getBoolean(keys.automaticUpdates, false)
 
     fun hiddenCatalogues() = rxPrefs.getStringSet("hidden_catalogues", emptySet())

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

@@ -21,7 +21,7 @@ import rx.schedulers.Schedulers
 import rx.subjects.PublishSubject
 import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
-import java.util.NoSuchElementException
+import java.util.*
 
 /**
  * Presenter of [CatalogueFragment].

+ 56 - 12
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt

@@ -11,6 +11,7 @@ import android.support.v7.widget.SearchView
 import android.view.*
 import com.afollestad.materialdialogs.MaterialDialog
 import com.f2prateek.rx.preferences.Preference
+import eu.kanade.tachiyomi.Constants
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Category
 import eu.kanade.tachiyomi.data.database.models.Manga
@@ -83,6 +84,11 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
      */
     var isFilterUnread = false
 
+    /**
+     * Sorting mode for library
+     */
+    var sortingMode = 0
+
     /**
      * Number of manga per row in grid mode.
      */
@@ -123,8 +129,9 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
     override fun onCreate(savedState: Bundle?) {
         super.onCreate(savedState)
         setHasOptionsMenu(true)
-        isFilterDownloaded = preferences.filterDownloaded().get() as Boolean
-        isFilterUnread = preferences.filterUnread().get() as Boolean
+        isFilterDownloaded = preferences.filterDownloaded().getOrDefault()
+        isFilterUnread = preferences.filterUnread().getOrDefault()
+        sortingMode = preferences.librarySortingMode().getOrDefault()
     }
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
@@ -179,12 +186,37 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
         super.onSaveInstanceState(outState)
     }
 
-    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
-        inflater.inflate(R.menu.library, menu)
-
+    /**
+     * Prepare the Fragment host's standard options menu to be displayed.  This is
+     * called right before the menu is shown, every time it is shown.  You can
+     * use this method to efficiently enable/disable items or otherwise
+     * dynamically modify the contents.
+     *
+     * @param menu The options menu as last shown or first initialized by
+     */
+    override fun onPrepareOptionsMenu(menu: Menu) {
         // Initialize search menu
         val filterDownloadedItem = menu.findItem(R.id.action_filter_downloaded)
         val filterUnreadItem = menu.findItem(R.id.action_filter_unread)
+        val sortModeAlpha = menu.findItem(R.id.action_sort_alpha)
+        val sortModeLastRead = menu.findItem(R.id.action_sort_last_read)
+        val sortModeLastUpdated = menu.findItem(R.id.action_sort_last_updated)
+
+        // Set correct checkbox filter
+        filterDownloadedItem.isChecked = isFilterDownloaded
+        filterUnreadItem.isChecked = isFilterUnread
+
+        // Set correct radio button sort
+        when (sortingMode) {
+            Constants.SORT_LIBRARY_ALPHA -> sortModeAlpha.isChecked = true
+            Constants.SORT_LIBRARY_LAST_READ -> sortModeLastRead.isChecked = true
+            Constants.SORT_LIBRARY_LAST_UPDATED -> sortModeLastUpdated.isChecked = true
+        }
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        inflater.inflate(R.menu.library, menu)
+
         val searchItem = menu.findItem(R.id.action_search)
         val searchView = searchItem.actionView as SearchView
 
@@ -194,9 +226,6 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
             searchView.clearFocus()
         }
 
-        filterDownloadedItem.isChecked = isFilterDownloaded
-        filterUnreadItem.isChecked = isFilterUnread
-
         searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
             override fun onQueryTextSubmit(query: String): Boolean {
                 onSearchTextChange(query)
@@ -219,7 +248,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
                 // Update settings.
                 preferences.filterUnread().set(isFilterUnread)
                 // Apply filter.
-                onFilterCheckboxChanged()
+                onFilterOrSortChanged()
             }
             R.id.action_filter_downloaded -> {
                 // Change downloaded filter status.
@@ -227,7 +256,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
                 // Update settings.
                 preferences.filterDownloaded().set(isFilterDownloaded)
                 // Apply filter.
-                onFilterCheckboxChanged()
+                onFilterOrSortChanged()
             }
             R.id.action_filter_empty -> {
                 // Remove filter status.
@@ -237,7 +266,22 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
                 preferences.filterUnread().set(isFilterUnread)
                 preferences.filterDownloaded().set(isFilterDownloaded)
                 // Apply filter
-                onFilterCheckboxChanged()
+                onFilterOrSortChanged()
+            }
+            R.id.action_sort_alpha -> {
+                sortingMode = Constants.SORT_LIBRARY_ALPHA
+                preferences.librarySortingMode().set(sortingMode)
+                onFilterOrSortChanged()
+            }
+            R.id.action_sort_last_read -> {
+                sortingMode = Constants.SORT_LIBRARY_LAST_READ
+                preferences.librarySortingMode().set(sortingMode)
+                onFilterOrSortChanged()
+            }
+            R.id.action_sort_last_updated -> {
+                sortingMode = Constants.SORT_LIBRARY_LAST_UPDATED
+                preferences.librarySortingMode().set(sortingMode)
+                onFilterOrSortChanged()
             }
             R.id.action_library_display_mode -> swapDisplayMode()
             R.id.action_update_library -> {
@@ -256,7 +300,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
     /**
      * Applies filter change
      */
-    private fun onFilterCheckboxChanged() {
+    private fun onFilterOrSortChanged() {
         presenter.resubscribeLibrary()
         activity.supportInvalidateOptionsMenu()
     }

+ 32 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt

@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.library
 
 import android.os.Bundle
 import android.util.Pair
+import eu.kanade.tachiyomi.Constants
 import eu.kanade.tachiyomi.data.cache.CoverCache
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
 import eu.kanade.tachiyomi.data.database.models.Category
@@ -133,10 +134,12 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
      */
     fun getLibraryMangasObservable(): Observable<Map<Int, List<Manga>>> {
         return db.getLibraryMangas().asRxObservable()
-                .flatMap { mangas ->
-                    Observable.from(mangas)
+                .flatMap {
+                    Observable.from(it)
                             // Filter library by options
                             .filter { filterManga(it) }
+                            .toSortedList { manga1, manga2 -> sortManga(manga1, manga2) }
+                            .flatMap { Observable.from(it) }
                             .groupBy { it.category }
                             .flatMap { group -> group.toList().map { Pair(group.key, it) } }
                             .toMap({ it.first }, { it.second })
@@ -159,6 +162,33 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
         start(GET_LIBRARY)
     }
 
+    /**
+     * Compares the two manga determined by sorting mode.
+     * Returns zero if this object is equal to the specified other object,
+     * a negative number if it's less than other, or a positive number if it's greater than other.
+     *
+     * @param manga1 first manga to compare
+     * @param manga2 second manga to compare
+     */
+    fun sortManga(manga1: Manga, manga2: Manga): Int {
+        when (preferences.librarySortingMode().getOrDefault()) {
+            Constants.SORT_LIBRARY_ALPHA -> return manga1.title.compareTo(manga2.title)
+            Constants.SORT_LIBRARY_LAST_READ -> {
+                var a = 0L
+                var b = 0L
+                manga1.id?.let { manga1Id ->
+                    manga2.id?.let { manga2Id ->
+                        db.getLastHistoryByMangaId(manga1Id).executeAsBlocking()?.let { a = it.last_read }
+                        db.getLastHistoryByMangaId(manga2Id).executeAsBlocking()?.let { b = it.last_read }
+                    }
+                }
+                return b.compareTo(a)
+            }
+            Constants.SORT_LIBRARY_LAST_UPDATED -> return manga2.last_update.compareTo(manga1.last_update)
+            else -> return manga1.title.compareTo(manga2.title)
+        }
+    }
+
     /**
      * Filters an entry of the library.
      *

+ 9 - 0
app/src/main/res/drawable/ic_sort_white_24dp.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
+</vector>

+ 28 - 4
app/src/main/res/menu/library.xml

@@ -1,6 +1,7 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
+      xmlns:app="http://schemas.android.com/apk/res-auto"
+      xmlns:tools="http://schemas.android.com/tools"
+      tools:context=".MainActivity">
 
     <item
         android:id="@+id/action_search"
@@ -29,11 +30,34 @@
         </menu>
     </item>
 
+    <item
+        android:id="@+id/action_sort"
+        android:icon="@drawable/ic_sort_white_24dp"
+        android:title="@string/action_sort"
+        app:showAsAction="never"
+        >
+        <menu>
+            <group
+                android:id="@+id/sort_group"
+                android:checkableBehavior="single">
+                <item
+                    android:id="@+id/action_sort_alpha"
+                    android:title="@string/action_sort_alpha"/>
+                <item
+                    android:id="@+id/action_sort_last_read"
+                    android:title="@string/action_sort_last_read"/>
+                <item
+                    android:id="@+id/action_sort_last_updated"
+                    android:title="@string/action_sort_last_updated"/>
+            </group>
+        </menu>
+    </item>
+
     <item
         android:id="@+id/action_update_library"
         android:icon="@drawable/ic_refresh_white_24dp"
         android:title="@string/action_update_library"
-        app:showAsAction="ifRoom" />
+        app:showAsAction="ifRoom"/>
 
     <item
         android:id="@+id/action_library_display_mode"
@@ -43,6 +67,6 @@
     <item
         android:id="@+id/action_edit_categories"
         android:title="@string/action_edit_categories"
-        app:showAsAction="never" />
+        app:showAsAction="never"/>
 
 </menu>

+ 8 - 0
app/src/main/res/raw/changelog_debug.xml

@@ -28,4 +28,12 @@
         </changelogtext>
     </changelogversion>
 
+    <changelogversion
+            changeDate=""
+            versionName="r359">
+        <changelogtext>Library sort for "last updated" will only work with manga updated after this
+            version.
+        </changelogtext>
+    </changelogversion>
+
 </changelog>

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

@@ -41,6 +41,7 @@
     <string name="pref_read_with_tapping_key">reader_tap</string>
     <string name="pref_filter_downloaded_key">pref_filter_downloaded_key</string>
     <string name="pref_filter_unread_key">pref_filter_unread_key</string>
+    <string name="pref_library_sorting_mode_key">library_sorting_mode</string>
 
     <string name="pref_download_directory_key">download_directory</string>
     <string name="pref_download_slots_key">pref_download_slots_key</string>

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

@@ -23,6 +23,9 @@
     <string name="action_filter_unread">Unread</string>
     <string name="action_filter_read">Read</string>
     <string name="action_filter_empty">Remove filter</string>
+    <string name="action_sort_alpha">Alphabetically</string>
+    <string name="action_sort_last_read">Last read</string>
+    <string name="action_sort_last_updated">Last updated</string>
     <string name="action_search">Search</string>
     <string name="action_select_all">Select all</string>
     <string name="action_mark_as_read">Mark as read</string>