mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 05:51:49 +01:00
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:
parent
d971768056
commit
aba528b227
@ -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
|
||||
}
|
||||
|
@ -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,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()
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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].
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
app/src/main/res/drawable/ic_sort_white_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_sort_white_24dp.xml
Normal 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>
|
@ -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: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>
|
||||
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user