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
This commit is contained in:
Bram van de Kerkhof 2016-12-04 20:22:12 +01:00 committed by inorichi
parent d971768056
commit aba528b227
16 changed files with 215 additions and 21 deletions

View File

@ -6,4 +6,8 @@ object Constants {
const val NOTIFICATION_DOWNLOAD_CHAPTER_ID = 3 const val NOTIFICATION_DOWNLOAD_CHAPTER_ID = 3
const val NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID = 4 const val NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID = 4
const val NOTIFICATION_DOWNLOAD_IMAGE_ID = 5 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
} }

View File

@ -40,6 +40,15 @@ interface HistoryQueries : DbProvider {
.build()) .build())
.prepare() .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. * Updates the history last read.

View File

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

View File

@ -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} 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. * Query to get the categories for a manga.
*/ */

View File

@ -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)
}
}

View File

@ -251,7 +251,13 @@ class LibraryUpdateService : Service() {
.map { manga } .map { manga }
} }
// Add manga with new chapters to the list. // 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. // Notify result of the overall update.
.doOnCompleted { .doOnCompleted {
if (newUpdates.isEmpty()) { if (newUpdates.isEmpty()) {

View File

@ -83,6 +83,8 @@ class PreferenceKeys(context: Context) {
val filterUnread = context.getString(R.string.pref_filter_unread_key) 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 automaticUpdates = context.getString(R.string.pref_enable_automatic_updates_key)
val startScreen = context.getString(R.string.pref_start_screen_key) val startScreen = context.getString(R.string.pref_start_screen_key)

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import android.support.v7.widget.SearchView
import android.view.* import android.view.*
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import eu.kanade.tachiyomi.Constants
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
@ -83,6 +84,11 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
*/ */
var isFilterUnread = false var isFilterUnread = false
/**
* Sorting mode for library
*/
var sortingMode = 0
/** /**
* Number of manga per row in grid mode. * Number of manga per row in grid mode.
*/ */
@ -123,8 +129,9 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
isFilterDownloaded = preferences.filterDownloaded().get() as Boolean isFilterDownloaded = preferences.filterDownloaded().getOrDefault()
isFilterUnread = preferences.filterUnread().get() as Boolean isFilterUnread = preferences.filterUnread().getOrDefault()
sortingMode = preferences.librarySortingMode().getOrDefault()
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
@ -179,12 +186,37 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
super.onSaveInstanceState(outState) 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 // Initialize search menu
val filterDownloadedItem = menu.findItem(R.id.action_filter_downloaded) val filterDownloadedItem = menu.findItem(R.id.action_filter_downloaded)
val filterUnreadItem = menu.findItem(R.id.action_filter_unread) 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 searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView val searchView = searchItem.actionView as SearchView
@ -194,9 +226,6 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
searchView.clearFocus() searchView.clearFocus()
} }
filterDownloadedItem.isChecked = isFilterDownloaded
filterUnreadItem.isChecked = isFilterUnread
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
onSearchTextChange(query) onSearchTextChange(query)
@ -219,7 +248,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
// Update settings. // Update settings.
preferences.filterUnread().set(isFilterUnread) preferences.filterUnread().set(isFilterUnread)
// Apply filter. // Apply filter.
onFilterCheckboxChanged() onFilterOrSortChanged()
} }
R.id.action_filter_downloaded -> { R.id.action_filter_downloaded -> {
// Change downloaded filter status. // Change downloaded filter status.
@ -227,7 +256,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
// Update settings. // Update settings.
preferences.filterDownloaded().set(isFilterDownloaded) preferences.filterDownloaded().set(isFilterDownloaded)
// Apply filter. // Apply filter.
onFilterCheckboxChanged() onFilterOrSortChanged()
} }
R.id.action_filter_empty -> { R.id.action_filter_empty -> {
// Remove filter status. // Remove filter status.
@ -237,7 +266,22 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
preferences.filterUnread().set(isFilterUnread) preferences.filterUnread().set(isFilterUnread)
preferences.filterDownloaded().set(isFilterDownloaded) preferences.filterDownloaded().set(isFilterDownloaded)
// Apply filter // 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_library_display_mode -> swapDisplayMode()
R.id.action_update_library -> { R.id.action_update_library -> {
@ -256,7 +300,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
/** /**
* Applies filter change * Applies filter change
*/ */
private fun onFilterCheckboxChanged() { private fun onFilterOrSortChanged() {
presenter.resubscribeLibrary() presenter.resubscribeLibrary()
activity.supportInvalidateOptionsMenu() activity.supportInvalidateOptionsMenu()
} }

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.library
import android.os.Bundle import android.os.Bundle
import android.util.Pair import android.util.Pair
import eu.kanade.tachiyomi.Constants
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
@ -133,10 +134,12 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
*/ */
fun getLibraryMangasObservable(): Observable<Map<Int, List<Manga>>> { fun getLibraryMangasObservable(): Observable<Map<Int, List<Manga>>> {
return db.getLibraryMangas().asRxObservable() return db.getLibraryMangas().asRxObservable()
.flatMap { mangas -> .flatMap {
Observable.from(mangas) Observable.from(it)
// Filter library by options // Filter library by options
.filter { filterManga(it) } .filter { filterManga(it) }
.toSortedList { manga1, manga2 -> sortManga(manga1, manga2) }
.flatMap { Observable.from(it) }
.groupBy { it.category } .groupBy { it.category }
.flatMap { group -> group.toList().map { Pair(group.key, it) } } .flatMap { group -> group.toList().map { Pair(group.key, it) } }
.toMap({ it.first }, { it.second }) .toMap({ it.first }, { it.second })
@ -159,6 +162,33 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
start(GET_LIBRARY) 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. * Filters an entry of the library.
* *

View File

@ -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>

View File

@ -1,6 +1,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity">
<item <item
android:id="@+id/action_search" android:id="@+id/action_search"
@ -29,11 +30,34 @@
</menu> </menu>
</item> </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 <item
android:id="@+id/action_update_library" android:id="@+id/action_update_library"
android:icon="@drawable/ic_refresh_white_24dp" android:icon="@drawable/ic_refresh_white_24dp"
android:title="@string/action_update_library" android:title="@string/action_update_library"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/action_library_display_mode" android:id="@+id/action_library_display_mode"
@ -43,6 +67,6 @@
<item <item
android:id="@+id/action_edit_categories" android:id="@+id/action_edit_categories"
android:title="@string/action_edit_categories" android:title="@string/action_edit_categories"
app:showAsAction="never" /> app:showAsAction="never"/>
</menu> </menu>

View File

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

View File

@ -41,6 +41,7 @@
<string name="pref_read_with_tapping_key">reader_tap</string> <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_downloaded_key">pref_filter_downloaded_key</string>
<string name="pref_filter_unread_key">pref_filter_unread_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_directory_key">download_directory</string>
<string name="pref_download_slots_key">pref_download_slots_key</string> <string name="pref_download_slots_key">pref_download_slots_key</string>

View File

@ -23,6 +23,9 @@
<string name="action_filter_unread">Unread</string> <string name="action_filter_unread">Unread</string>
<string name="action_filter_read">Read</string> <string name="action_filter_read">Read</string>
<string name="action_filter_empty">Remove filter</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_search">Search</string>
<string name="action_select_all">Select all</string> <string name="action_select_all">Select all</string>
<string name="action_mark_as_read">Mark as read</string> <string name="action_mark_as_read">Mark as read</string>