diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.java b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.java index 713c055036..f182f2e4f7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.java +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.java @@ -378,17 +378,21 @@ public class DownloadManager { savePageList(download.source, download.manga, download.chapter, download.pages); } - // Get the absolute path to the chapter directory - public File getAbsoluteChapterDirectory(Source source, Manga manga, Chapter chapter) { + public File getAbsoluteMangaDirectory(Source source, Manga manga) { String chapterRelativePath = source.getName() + File.separator + - manga.title.replaceAll("[^\\sa-zA-Z0-9.-]", "_") + - File.separator + - chapter.name.replaceAll("[^\\sa-zA-Z0-9.-]", "_"); + manga.title.replaceAll("[^\\sa-zA-Z0-9.-]", "_"); 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 private File getAbsoluteChapterDirectory(Download download) { return getAbsoluteChapterDirectory(download.source, download.manga, download.chapter); diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index c5ee1cefce..c5adab4616 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -11,6 +11,10 @@ import eu.kanade.tachiyomi.data.source.base.Source import java.io.File import java.io.IOException +fun Preference.getOrDefault(): T { + return get() ?: defaultValue()!! +} + class PreferencesHelper(private val context: 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) } + fun filterDownloaded(): Preference { + return rxPrefs.getBoolean(getKey(R.string.pref_filter_downloaded), false) + } + + fun filterUnread(): Preference { + return rxPrefs.getBoolean(getKey(R.string.pref_filter_unread), false) + } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt index 50950803b1..7a0403920d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt @@ -74,6 +74,17 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback */ private var selectedCoverManga: Manga? = null + + /** + * TODO + */ + var isFilterDownloaded = false + + /** + * TODO + */ + var isFilterUnread = false + companion object { /** * Key to change the cover of a manga in [onActivityResult]. @@ -104,6 +115,8 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) 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? { @@ -116,6 +129,14 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback appBar = (activity as MainActivity).appBar 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) adapter = LibraryAdapter(childFragmentManager) @@ -144,6 +165,8 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback inflater.inflate(R.menu.library, 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 searchView = searchItem.actionView as SearchView @@ -153,6 +176,9 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback searchView.clearFocus() } + filterDownloadedItem.isChecked = isFilterDownloaded; + filterUnreadItem.isChecked = isFilterUnread; + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { onSearchTextChange(query) @@ -168,6 +194,32 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback override fun onOptionsItemSelected(item: MenuItem): Boolean { 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_edit_categories -> { val intent = CategoryActivity.newIntent(activity) @@ -179,6 +231,16 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback return true } + /** + * Applies filter change + */ + private fun onFilterCheckboxChanged() { + presenter.updateLibrary() + adapter.notifyDataSetChanged() + adapter.refreshRegisteredAdapters() + activity.supportInvalidateOptionsMenu(); + } + /** * Updates the query. * @@ -211,6 +273,10 @@ class LibraryFragment : BaseRxFragment(), ActionMode.Callback // Restore active category. view_pager.setCurrentItem(activeCat, false) if (tabs.tabCount > 0) { + // Prevent IndexOutOfBoundsException + if (tabs.tabCount <= view_pager.currentItem) { + view_pager.currentItem = (tabs.tabCount - 1) + } tabs.getTabAt(view_pager.currentItem)?.select() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 23269398fd..7e0bf1bf60 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -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.Manga 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.getOrDefault import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.event.LibraryMangasEvent import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter @@ -60,6 +62,11 @@ class LibraryPresenter : BasePresenter() { */ @Inject lateinit var sourceManager: SourceManager + /** + * Download manager. + */ + @Inject lateinit var downloadManager: DownloadManager + companion object { /** * Id of the restartable that listens for library updates. @@ -107,6 +114,13 @@ class LibraryPresenter : BasePresenter() { .observeOn(AndroidSchedulers.mainThread()) } + /** + * Update the library information + */ + fun updateLibrary() { + start(GET_LIBRARY) + } + /** * Get the categories from the database. * @@ -125,13 +139,64 @@ class LibraryPresenter : BasePresenter() { */ fun getLibraryMangasObservable(): Observable>> { return db.libraryMangas.asRxObservable() - .flatMap { mangas -> Observable.from(mangas) - .groupBy { it.category } - .flatMap { group -> group.toList().map { Pair(group.key, it) } } - .toMap({ it.first }, { it.second }) + .flatMap { mangas -> + Observable.from(mangas) + .filter { + // 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. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.java b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.java index a488a4cb2e..d357b0503b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaActivity.java @@ -77,6 +77,13 @@ public class MangaActivity extends BaseRxActivity { adapter = new MangaDetailAdapter(getSupportFragmentManager(), this); 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); if (!isOnline) diff --git a/app/src/main/res/drawable/ic_filter_list.xml b/app/src/main/res/drawable/ic_filter_list.xml new file mode 100644 index 0000000000..7d435fa2b8 --- /dev/null +++ b/app/src/main/res/drawable/ic_filter_list.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/menu/library.xml b/app/src/main/res/menu/library.xml index 29639aa09c..474a8f9128 100644 --- a/app/src/main/res/menu/library.xml +++ b/app/src/main/res/menu/library.xml @@ -2,6 +2,26 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> + + + + + + + + pref_reader_theme_key pref_image_decoder_key pref_seamless_mode_key + pref_filter_downloaded + pref_filter_unread pref_download_directory_key pref_download_slots_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a4c231ec3a..fb62107288 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,6 +14,10 @@ Settings + Filter + Downloaded + Unread + Remove filter Search Refresh Select all diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index a86901edfd..6023805e51 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -15,7 +15,7 @@ @color/colorPrimarySuperDark @style/ThemeOverlay.AppCompat.Light @style/ThemeOverlay.AppCompat.Light - @color/white + @color/colorAccent