mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-22 21:41:50 +01:00
Merge pull request #198 from NoodleMage/issue_27
Added filter options. fix for Issue #27
This commit is contained in:
commit
bcbd541d48
@ -378,17 +378,21 @@ public class DownloadManager {
|
|||||||
savePageList(download.source, download.manga, download.chapter, download.pages);
|
savePageList(download.source, download.manga, download.chapter, download.pages);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the absolute path to the chapter directory
|
public File getAbsoluteMangaDirectory(Source source, Manga manga) {
|
||||||
public File getAbsoluteChapterDirectory(Source source, Manga manga, Chapter chapter) {
|
|
||||||
String chapterRelativePath = source.getName() +
|
String chapterRelativePath = source.getName() +
|
||||||
File.separator +
|
File.separator +
|
||||||
manga.title.replaceAll("[^\\sa-zA-Z0-9.-]", "_") +
|
manga.title.replaceAll("[^\\sa-zA-Z0-9.-]", "_");
|
||||||
File.separator +
|
|
||||||
chapter.name.replaceAll("[^\\sa-zA-Z0-9.-]", "_");
|
|
||||||
|
|
||||||
return new File(preferences.getDownloadsDirectory(), chapterRelativePath);
|
return new File(preferences.getDownloadsDirectory(), chapterRelativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the absolute path to the chapter directory
|
||||||
|
public File getAbsoluteChapterDirectory(Source source, Manga manga, Chapter chapter) {
|
||||||
|
String chapterRelativePath = chapter.name.replaceAll("[^\\sa-zA-Z0-9.-]", "_");
|
||||||
|
|
||||||
|
return new File(getAbsoluteMangaDirectory(source, manga), chapterRelativePath);
|
||||||
|
}
|
||||||
|
|
||||||
// Shortcut for the method above
|
// Shortcut for the method above
|
||||||
private File getAbsoluteChapterDirectory(Download download) {
|
private File getAbsoluteChapterDirectory(Download download) {
|
||||||
return getAbsoluteChapterDirectory(download.source, download.manga, download.chapter);
|
return getAbsoluteChapterDirectory(download.source, download.manga, download.chapter);
|
||||||
|
@ -11,6 +11,10 @@ import eu.kanade.tachiyomi.data.source.base.Source
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
fun <T> Preference<T>.getOrDefault(): T {
|
||||||
|
return get() ?: defaultValue()!!
|
||||||
|
}
|
||||||
|
|
||||||
class PreferencesHelper(private val context: Context) {
|
class PreferencesHelper(private val context: Context) {
|
||||||
|
|
||||||
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
@ -182,4 +186,12 @@ class PreferencesHelper(private val context: Context) {
|
|||||||
return rxPrefs.getInteger(getKey(R.string.pref_library_update_interval_key), 0)
|
return rxPrefs.getInteger(getKey(R.string.pref_library_update_interval_key), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun filterDownloaded(): Preference<Boolean> {
|
||||||
|
return rxPrefs.getBoolean(getKey(R.string.pref_filter_downloaded), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun filterUnread(): Preference<Boolean> {
|
||||||
|
return rxPrefs.getBoolean(getKey(R.string.pref_filter_unread), false)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,17 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
*/
|
*/
|
||||||
private var selectedCoverManga: Manga? = null
|
private var selectedCoverManga: Manga? = null
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
var isFilterDownloaded = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO
|
||||||
|
*/
|
||||||
|
var isFilterUnread = false
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Key to change the cover of a manga in [onActivityResult].
|
* Key to change the cover of a manga in [onActivityResult].
|
||||||
@ -104,6 +115,8 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
|
isFilterDownloaded = presenter.preferences.filterDownloaded().get() as Boolean
|
||||||
|
isFilterUnread = presenter.preferences.filterUnread().get() as Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
|
||||||
@ -116,6 +129,14 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
|
|
||||||
appBar = (activity as MainActivity).appBar
|
appBar = (activity as MainActivity).appBar
|
||||||
tabs = appBar.inflate(R.layout.library_tab_layout) as TabLayout
|
tabs = appBar.inflate(R.layout.library_tab_layout) as TabLayout
|
||||||
|
|
||||||
|
// Workaround to prevent: Tab belongs to a different TabLayout.
|
||||||
|
// Internal bug in Support library v23.2.0.
|
||||||
|
// See https://code.google.com/p/android/issues/detail?id=201827
|
||||||
|
for (j in 0..16) {
|
||||||
|
tabs.newTab()
|
||||||
|
}
|
||||||
|
|
||||||
appBar.addView(tabs)
|
appBar.addView(tabs)
|
||||||
|
|
||||||
adapter = LibraryAdapter(childFragmentManager)
|
adapter = LibraryAdapter(childFragmentManager)
|
||||||
@ -144,6 +165,8 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
inflater.inflate(R.menu.library, menu)
|
inflater.inflate(R.menu.library, menu)
|
||||||
|
|
||||||
// Initialize search menu
|
// Initialize search menu
|
||||||
|
val filterDownloadedItem = menu.findItem(R.id.action_filter_downloaded)
|
||||||
|
val filterUnreadItem = menu.findItem(R.id.action_filter_unread)
|
||||||
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
|
||||||
|
|
||||||
@ -153,6 +176,9 @@ 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)
|
||||||
@ -168,6 +194,32 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
|
R.id.action_filter_unread -> {
|
||||||
|
// Change unread filter status.
|
||||||
|
isFilterUnread = !isFilterUnread
|
||||||
|
// Update settings.
|
||||||
|
presenter.preferences.filterUnread().set(isFilterUnread)
|
||||||
|
// Apply filter.
|
||||||
|
onFilterCheckboxChanged()
|
||||||
|
}
|
||||||
|
R.id.action_filter_downloaded -> {
|
||||||
|
// Change downloaded filter status.
|
||||||
|
isFilterDownloaded = !isFilterDownloaded
|
||||||
|
// Update settings.
|
||||||
|
presenter.preferences.filterDownloaded().set(isFilterDownloaded)
|
||||||
|
// Apply filter.
|
||||||
|
onFilterCheckboxChanged()
|
||||||
|
}
|
||||||
|
R.id.action_filter_empty -> {
|
||||||
|
// Remove filter status.
|
||||||
|
isFilterUnread = false
|
||||||
|
isFilterDownloaded = false
|
||||||
|
// Update settings.
|
||||||
|
presenter.preferences.filterUnread().set(isFilterUnread)
|
||||||
|
presenter.preferences.filterDownloaded().set(isFilterDownloaded)
|
||||||
|
// Apply filter
|
||||||
|
onFilterCheckboxChanged()
|
||||||
|
}
|
||||||
R.id.action_refresh -> LibraryUpdateService.start(activity)
|
R.id.action_refresh -> LibraryUpdateService.start(activity)
|
||||||
R.id.action_edit_categories -> {
|
R.id.action_edit_categories -> {
|
||||||
val intent = CategoryActivity.newIntent(activity)
|
val intent = CategoryActivity.newIntent(activity)
|
||||||
@ -179,6 +231,16 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies filter change
|
||||||
|
*/
|
||||||
|
private fun onFilterCheckboxChanged() {
|
||||||
|
presenter.updateLibrary()
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
adapter.refreshRegisteredAdapters()
|
||||||
|
activity.supportInvalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the query.
|
* Updates the query.
|
||||||
*
|
*
|
||||||
@ -211,6 +273,10 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
|
|||||||
// Restore active category.
|
// Restore active category.
|
||||||
view_pager.setCurrentItem(activeCat, false)
|
view_pager.setCurrentItem(activeCat, false)
|
||||||
if (tabs.tabCount > 0) {
|
if (tabs.tabCount > 0) {
|
||||||
|
// Prevent IndexOutOfBoundsException
|
||||||
|
if (tabs.tabCount <= view_pager.currentItem) {
|
||||||
|
view_pager.currentItem = (tabs.tabCount - 1)
|
||||||
|
}
|
||||||
tabs.getTabAt(view_pager.currentItem)?.select()
|
tabs.getTabAt(view_pager.currentItem)?.select()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,9 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||||||
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
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.event.LibraryMangasEvent
|
import eu.kanade.tachiyomi.event.LibraryMangasEvent
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
@ -60,6 +62,11 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
*/
|
*/
|
||||||
@Inject lateinit var sourceManager: SourceManager
|
@Inject lateinit var sourceManager: SourceManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download manager.
|
||||||
|
*/
|
||||||
|
@Inject lateinit var downloadManager: DownloadManager
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Id of the restartable that listens for library updates.
|
* Id of the restartable that listens for library updates.
|
||||||
@ -107,6 +114,13 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the library information
|
||||||
|
*/
|
||||||
|
fun updateLibrary() {
|
||||||
|
start(GET_LIBRARY)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the categories from the database.
|
* Get the categories from the database.
|
||||||
*
|
*
|
||||||
@ -125,13 +139,64 @@ class LibraryPresenter : BasePresenter<LibraryFragment>() {
|
|||||||
*/
|
*/
|
||||||
fun getLibraryMangasObservable(): Observable<Map<Int, List<Manga>>> {
|
fun getLibraryMangasObservable(): Observable<Map<Int, List<Manga>>> {
|
||||||
return db.libraryMangas.asRxObservable()
|
return db.libraryMangas.asRxObservable()
|
||||||
.flatMap { mangas -> Observable.from(mangas)
|
.flatMap { mangas ->
|
||||||
.groupBy { it.category }
|
Observable.from(mangas)
|
||||||
.flatMap { group -> group.toList().map { Pair(group.key, it) } }
|
.filter {
|
||||||
.toMap({ it.first }, { it.second })
|
// Filter library by options
|
||||||
|
filterLibrary(it)
|
||||||
|
}
|
||||||
|
.groupBy { it.category }
|
||||||
|
.flatMap { group -> group.toList().map { Pair(group.key, it) } }
|
||||||
|
.toMap({ it.first }, { it.second })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter library by preference
|
||||||
|
*
|
||||||
|
* @param manga from library
|
||||||
|
* @return filter status
|
||||||
|
*/
|
||||||
|
fun filterLibrary(manga: Manga): Boolean {
|
||||||
|
val prefFilterDownloaded = preferences.filterDownloaded().getOrDefault()
|
||||||
|
val prefFilterUnread = preferences.filterUnread().getOrDefault()
|
||||||
|
|
||||||
|
// Check if filter option is selected
|
||||||
|
if (prefFilterDownloaded || prefFilterUnread) {
|
||||||
|
|
||||||
|
// Does it have downloaded chapters.
|
||||||
|
var hasDownloaded = false
|
||||||
|
var hasUnread = false
|
||||||
|
|
||||||
|
if (prefFilterUnread) {
|
||||||
|
// Does it have unread chapters.
|
||||||
|
hasUnread = manga.unread > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefFilterDownloaded) {
|
||||||
|
val mangaDir = downloadManager.getAbsoluteMangaDirectory(sourceManager.get(manga.source), manga)
|
||||||
|
|
||||||
|
if (mangaDir.exists()) {
|
||||||
|
for (file in mangaDir.listFiles()) {
|
||||||
|
if (file.isDirectory && file.listFiles().isNotEmpty()) {
|
||||||
|
hasDownloaded = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return correct filter status
|
||||||
|
if (prefFilterDownloaded && prefFilterUnread) {
|
||||||
|
return (hasDownloaded && hasUnread)
|
||||||
|
} else {
|
||||||
|
return (hasDownloaded || hasUnread)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a manga is opened.
|
* Called when a manga is opened.
|
||||||
*/
|
*/
|
||||||
|
@ -77,6 +77,13 @@ public class MangaActivity extends BaseRxActivity<MangaPresenter> {
|
|||||||
adapter = new MangaDetailAdapter(getSupportFragmentManager(), this);
|
adapter = new MangaDetailAdapter(getSupportFragmentManager(), this);
|
||||||
|
|
||||||
viewPager.setAdapter(adapter);
|
viewPager.setAdapter(adapter);
|
||||||
|
|
||||||
|
// Workaround to prevent: Tab belongs to a different TabLayout.
|
||||||
|
// Internal bug in Support library v23.2.0.
|
||||||
|
// See https://code.google.com/p/android/issues/detail?id=201827
|
||||||
|
for (int j = 0; j < 17; j++)
|
||||||
|
tabs.newTab();
|
||||||
|
|
||||||
tabs.setupWithViewPager(viewPager);
|
tabs.setupWithViewPager(viewPager);
|
||||||
|
|
||||||
if (!isOnline)
|
if (!isOnline)
|
||||||
|
9
app/src/main/res/drawable/ic_filter_list.xml
Normal file
9
app/src/main/res/drawable/ic_filter_list.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="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z"/>
|
||||||
|
</vector>
|
@ -2,6 +2,26 @@
|
|||||||
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
|
||||||
|
android:id="@+id/action_filter"
|
||||||
|
android:title="@string/action_filter"
|
||||||
|
android:icon="@drawable/ic_filter_list"
|
||||||
|
app:showAsAction="ifRoom">
|
||||||
|
<menu>
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_filter_downloaded"
|
||||||
|
android:checkable="true"
|
||||||
|
android:title="@string/action_filter_downloaded"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_filter_unread"
|
||||||
|
android:checkable="true"
|
||||||
|
android:title="@string/action_filter_unread"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_filter_empty"
|
||||||
|
android:title="@string/action_filter_empty"/>
|
||||||
|
</menu>
|
||||||
|
</item>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_search"
|
android:id="@+id/action_search"
|
||||||
android:title="@string/action_search"
|
android:title="@string/action_search"
|
||||||
|
@ -28,6 +28,8 @@
|
|||||||
<string name="pref_reader_theme_key">pref_reader_theme_key</string>
|
<string name="pref_reader_theme_key">pref_reader_theme_key</string>
|
||||||
<string name="pref_image_decoder_key">pref_image_decoder_key</string>
|
<string name="pref_image_decoder_key">pref_image_decoder_key</string>
|
||||||
<string name="pref_seamless_mode_key">pref_seamless_mode_key</string>
|
<string name="pref_seamless_mode_key">pref_seamless_mode_key</string>
|
||||||
|
<string name="pref_filter_downloaded">pref_filter_downloaded</string>
|
||||||
|
<string name="pref_filter_unread">pref_filter_unread</string>
|
||||||
|
|
||||||
<string name="pref_download_directory_key">pref_download_directory_key</string>
|
<string name="pref_download_directory_key">pref_download_directory_key</string>
|
||||||
<string name="pref_download_slots_key">pref_download_slots_key</string>
|
<string name="pref_download_slots_key">pref_download_slots_key</string>
|
||||||
|
@ -14,6 +14,10 @@
|
|||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<string name="action_settings">Settings</string>
|
<string name="action_settings">Settings</string>
|
||||||
|
<string name="action_filter">Filter</string>
|
||||||
|
<string name="action_filter_downloaded">Downloaded</string>
|
||||||
|
<string name="action_filter_unread">Unread</string>
|
||||||
|
<string name="action_filter_empty">Remove filter</string>
|
||||||
<string name="action_search">Search</string>
|
<string name="action_search">Search</string>
|
||||||
<string name="action_refresh">Refresh</string>
|
<string name="action_refresh">Refresh</string>
|
||||||
<string name="action_select_all">Select all</string>
|
<string name="action_select_all">Select all</string>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<item name="actionModeBackground">@color/colorPrimarySuperDark</item>
|
<item name="actionModeBackground">@color/colorPrimarySuperDark</item>
|
||||||
<item name="actionBarPopupTheme">@style/ThemeOverlay.AppCompat.Light</item>
|
<item name="actionBarPopupTheme">@style/ThemeOverlay.AppCompat.Light</item>
|
||||||
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
|
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
|
||||||
<item name="colorAccent">@color/white</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppTheme.Overlay.Dark" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
|
<style name="AppTheme.Overlay.Dark" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||||
|
Loading…
Reference in New Issue
Block a user