Browse Source

Merge branch 'dev' into dev-settings-search

mpm11011 4 years ago
parent
commit
528c1b90c2
100 changed files with 732 additions and 544 deletions
  1. 1 1
      .github/ISSUE_TEMPLATE.md
  2. 1 1
      .github/ISSUE_TEMPLATE/bug_report.md
  3. 1 1
      .github/ISSUE_TEMPLATE/feature_request.md
  4. 2 2
      README.md
  5. 7 11
      app/build.gradle
  6. 1 1
      app/src/main/java/eu/kanade/tachiyomi/App.kt
  7. 23 0
      app/src/main/java/eu/kanade/tachiyomi/Migrations.kt
  8. 5 3
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt
  9. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
  10. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt
  11. 11 5
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt
  12. 2 2
      app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt
  13. 5 0
      app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt
  14. 16 6
      app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt
  15. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt
  16. 4 2
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt
  17. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
  18. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt
  19. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt
  20. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaThumbnailModelLoader.kt
  21. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt
  22. 5 3
      app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
  23. 10 5
      app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt
  24. 10 3
      app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
  25. 10 6
      app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
  26. 18 9
      app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt
  27. 15 3
      app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
  28. 33 8
      app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
  29. 9 4
      app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt
  30. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt
  31. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt
  32. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt
  33. 7 7
      app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt
  34. 0 23
      app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateChecker.kt
  35. 7 4
      app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt
  36. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt
  37. 0 13
      app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoRelease.kt
  38. 0 41
      app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoUpdateChecker.kt
  39. 0 9
      app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoUpdateResult.kt
  40. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubRelease.kt
  41. 4 3
      app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubService.kt
  42. 26 6
      app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateChecker.kt
  43. 5 3
      app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt
  44. 1 1
      app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt
  45. 2 2
      app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt
  46. 7 1
      app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt
  47. 3 3
      app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt
  48. 3 3
      app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt
  49. 19 17
      app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt
  50. 1 1
      app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt
  51. 1 1
      app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt
  52. 4 4
      app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt
  53. 3 3
      app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt
  54. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt
  55. 32 28
      app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt
  56. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionPresenter.kt
  57. 1 4
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsController.kt
  58. 0 3
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt
  59. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt
  60. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesPresenter.kt
  61. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceFilterController.kt
  62. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcePresenter.kt
  63. 0 2
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt
  64. 4 2
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
  65. 7 3
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceItem.kt
  66. 2 1
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/filter/SelectItem.kt
  67. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/filter/TriStateItem.kt
  68. 12 10
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt
  69. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchPresenter.kt
  70. 5 2
      app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt
  71. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt
  72. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
  73. 5 2
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt
  74. 36 23
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
  75. 32 19
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt
  76. 22 20
      app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
  77. 15 10
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
  78. 6 1
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
  79. 0 59
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterDividerItemDecoration.kt
  80. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt
  81. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt
  82. 28 6
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSettingsSheet.kt
  83. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/MangaChaptersHeaderAdapter.kt
  84. 48 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/SetChapterSettingsDialog.kt
  85. 50 25
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
  86. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackReadingDatesDialog.kt
  87. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackHolder.kt
  88. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt
  89. 6 4
      app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
  90. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt
  91. 33 21
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
  92. 39 29
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt
  93. 4 4
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
  94. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt
  95. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt
  96. 3 3
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt
  97. 3 3
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt
  98. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt
  99. 15 0
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt
  100. 6 3
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressBar.kt

+ 1 - 1
.github/ISSUE_TEMPLATE.md

@@ -2,7 +2,7 @@
 
 I acknowledge that:
 
-- I have updated to the latest version of the app (stable is v0.10.4)
+- I have updated to the latest version of the app (stable is v0.10.5)
 - I have updated all extensions
 - If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
 

+ 1 - 1
.github/ISSUE_TEMPLATE/bug_report.md

@@ -9,7 +9,7 @@ labels: "bug"
 
 I acknowledge that:
 
-- I have updated to the latest version of the app (stable is v0.10.4)
+- I have updated to the latest version of the app (stable is v0.10.5)
 - I have updated all extensions
 - If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
 

+ 1 - 1
.github/ISSUE_TEMPLATE/feature_request.md

@@ -9,7 +9,7 @@ labels: "feature"
 
 I acknowledge that:
 
-- I have updated to the latest version of the app (stable is v0.10.4)
+- I have updated to the latest version of the app (stable is v0.10.5)
 - I have updated all extensions
 - If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
 

+ 2 - 2
README.md

@@ -1,6 +1,6 @@
 | Build | Stable | Weekly Preview | Contribute | Support Server |
 |-------|----------|---------|------------|---------|
-| [![Travis](https://img.shields.io/travis/inorichi/tachiyomi.svg)](https://travis-ci.org/inorichi/tachiyomi) | [![stable release](https://img.shields.io/github/release/inorichi/tachiyomi.svg?maxAge=3600&label=download)](https://github.com/inorichi/tachiyomi/releases) | [![latest weekly build](https://img.shields.io/badge/download-latest%20build-blue.svg)](http://tachiyomi.kanade.eu/latest) | [![Translation status](https://hosted.weblate.org/widgets/tachiyomi/-/svg-badge.svg)](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [![Discord](https://img.shields.io/discord/349436576037732353.svg)](https://discord.gg/tachiyomi) |
+| [![Travis](https://img.shields.io/travis/inorichi/tachiyomi.svg)](https://travis-ci.org/inorichi/tachiyomi) | [![stable release](https://img.shields.io/github/release/inorichi/tachiyomi.svg?maxAge=3600&label=download)](https://github.com/inorichi/tachiyomi/releases) | [![latest weekly build](https://img.shields.io/github/v/release/tachiyomiorg/android-app-preview.svg?maxAge=3600&label=download)](https://github.com/tachiyomiorg/android-app-preview/releases) | [![Translation status](https://hosted.weblate.org/widgets/tachiyomi/-/svg-badge.svg)](https://hosted.weblate.org/engage/tachiyomi/?utm_source=widget) | [![Discord](https://img.shields.io/discord/349436576037732353.svg)](https://discord.gg/tachiyomi) |
 
 
 # ![app icon](./.github/readme-images/app-icon.png)Tachiyomi
@@ -23,7 +23,7 @@ Features include:
 ## Download
 Get the app from our [releases page](https://github.com/inorichi/tachiyomi/releases).
 
-If you want to try new features before they get to the stable release, you can download the preview version [here](http://tachiyomi.kanade.eu/latest).
+If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/tachiyomiorg/android-app-preview/releases).
 
 ## Issues, Feature Requests and Contributing
 

+ 7 - 11
app/build.gradle

@@ -40,8 +40,8 @@ android {
         minSdkVersion AndroidConfig.minSdk
         targetSdkVersion AndroidConfig.targetSdk
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-        versionCode 50
-        versionName "0.10.4"
+        versionCode 51
+        versionName "0.10.5"
 
         buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
         buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
@@ -133,7 +133,7 @@ dependencies {
     implementation 'androidx.biometric:biometric:1.1.0-alpha02'
     implementation 'androidx.browser:browser:1.2.0'
     implementation 'androidx.cardview:cardview:1.0.0'
-    implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
+    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
     implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
     implementation 'androidx.core:core-ktx:1.4.0-alpha01'
     implementation 'androidx.multidex:multidex:2.0.1'
@@ -163,14 +163,14 @@ dependencies {
     implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
 
     // Network client
-    final okhttp_version = '4.8.1'
+    final okhttp_version = '4.9.0'
     implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
     implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
     implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version"
-    implementation 'com.squareup.okio:okio:2.7.0'
+    implementation 'com.squareup.okio:okio:2.8.0'
 
     // TLS 1.3 support for Android < 10
-    implementation 'org.conscrypt:conscrypt-android:2.4.0'
+    implementation 'org.conscrypt:conscrypt-android:2.5.1'
 
     // REST
     final retrofit_version = '2.9.0'
@@ -281,10 +281,6 @@ dependencies {
 
     // For detecting memory leaks; see https://square.github.io/leakcanary/
 //    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
-
-    // Debug tool; see https://fbflipper.com/
-//    debugImplementation 'com.facebook.flipper:flipper:0.50.0'
-//    debugImplementation 'com.facebook.soloader:soloader:0.9.0'
 }
 
 buildscript {
@@ -312,7 +308,7 @@ task copyResources(type: Copy) {
     include '**/*'
 }
 
-preBuild.dependsOn(ktlintFormat, copyResources)
+preBuild.dependsOn(formatKotlin, copyResources)
 
 if (getGradle().getStartParameter().getTaskRequests().toString().contains("Standard")) {
     apply plugin: 'com.google.gms.google-services'

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/App.kt

@@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.notification.Notifications
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
 import eu.kanade.tachiyomi.util.system.LocaleHelper
-import java.security.Security
 import org.acra.ACRA
 import org.acra.annotation.AcraCore
 import org.acra.annotation.AcraHttpSender
@@ -24,6 +23,7 @@ import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.InjektScope
 import uy.kohesive.injekt.injectLazy
 import uy.kohesive.injekt.registry.default.DefaultRegistrar
+import java.security.Security
 
 @AcraCore(
     buildConfigClass = BuildConfig::class,

+ 23 - 0
app/src/main/java/eu/kanade/tachiyomi/Migrations.kt

@@ -1,11 +1,15 @@
 package eu.kanade.tachiyomi
 
+import androidx.core.content.edit
+import androidx.preference.PreferenceManager
 import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
 import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
+import eu.kanade.tachiyomi.data.preference.PreferenceKeys
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.data.updater.UpdaterJob
 import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
 import eu.kanade.tachiyomi.ui.library.LibrarySort
+import eu.kanade.tachiyomi.widget.ExtendedNavigationView
 import java.io.File
 
 object Migrations {
@@ -89,6 +93,25 @@ object Migrations {
                     preferences.librarySortingMode().set(LibrarySort.ALPHA)
                 }
             }
+            if (oldVersion < 52) {
+                // Migrate library filters to tri-state versions
+                val prefs = PreferenceManager.getDefaultSharedPreferences(context)
+                fun convertBooleanPrefToTriState(key: String): Int {
+                    val oldPrefValue = prefs.getBoolean(key, false)
+                    return if (oldPrefValue) ExtendedNavigationView.Item.TriStateGroup.STATE_INCLUDE
+                    else ExtendedNavigationView.Item.TriStateGroup.STATE_IGNORE
+                }
+                prefs.edit {
+                    putInt(PreferenceKeys.filterDownloaded, convertBooleanPrefToTriState("pref_filter_downloaded_key"))
+                    remove("pref_filter_downloaded_key")
+
+                    putInt(PreferenceKeys.filterUnread, convertBooleanPrefToTriState("pref_filter_unread_key"))
+                    remove("pref_filter_unread_key")
+
+                    putInt(PreferenceKeys.filterCompleted, convertBooleanPrefToTriState("pref_filter_completed_key"))
+                    remove("pref_filter_completed_key")
+                }
+            }
             return true
         }
         return false

+ 5 - 3
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt

@@ -8,9 +8,9 @@ import androidx.work.WorkManager
 import androidx.work.Worker
 import androidx.work.WorkerParameters
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
-import java.util.concurrent.TimeUnit
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.concurrent.TimeUnit
 
 class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
     Worker(context, workerParams) {
@@ -36,8 +36,10 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
             val interval = prefInterval ?: preferences.backupInterval().get()
             if (interval > 0) {
                 val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
-                    interval.toLong(), TimeUnit.HOURS,
-                    10, TimeUnit.MINUTES
+                    interval.toLong(),
+                    TimeUnit.HOURS,
+                    10,
+                    TimeUnit.MINUTES
                 )
                     .addTag(TAG)
                     .build()

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt

@@ -50,10 +50,10 @@ import eu.kanade.tachiyomi.source.LocalSource
 import eu.kanade.tachiyomi.source.Source
 import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
-import kotlin.math.max
 import rx.Observable
 import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
+import kotlin.math.max
 
 class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt

@@ -11,9 +11,9 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.util.storage.getUriCompat
 import eu.kanade.tachiyomi.util.system.notificationBuilder
 import eu.kanade.tachiyomi.util.system.notificationManager
+import uy.kohesive.injekt.injectLazy
 import java.io.File
 import java.util.concurrent.TimeUnit
-import uy.kohesive.injekt.injectLazy
 
 internal class BackupNotifier(private val context: Context) {
 

+ 11 - 5
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt

@@ -32,12 +32,9 @@ import eu.kanade.tachiyomi.data.database.models.TrackImpl
 import eu.kanade.tachiyomi.data.notification.Notifications
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.source.Source
+import eu.kanade.tachiyomi.util.chapter.NoChaptersException
 import eu.kanade.tachiyomi.util.system.acquireWakeLock
 import eu.kanade.tachiyomi.util.system.isServiceRunning
-import java.io.File
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
 import kotlinx.coroutines.CoroutineExceptionHandler
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.Job
@@ -45,6 +42,10 @@ import kotlinx.coroutines.launch
 import rx.Observable
 import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
 
 /**
  * Restores backup from a JSON file.
@@ -398,7 +399,12 @@ class BackupRestoreService : Service() {
         return backupManager.restoreChapterFetchObservable(source, manga, chapters)
             // If there's any error, return empty update and continue.
             .onErrorReturn {
-                errors.add(Date() to "${manga.title} - ${it.message}")
+                val errorMessage = if (it is NoChaptersException) {
+                    getString(R.string.no_chapters_error)
+                } else {
+                    it.message
+                }
+                errors.add(Date() to "${manga.title} - $errorMessage")
                 Pair(emptyList(), emptyList())
             }
     }

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt

@@ -9,13 +9,13 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
 import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.util.storage.DiskUtil
 import eu.kanade.tachiyomi.util.storage.saveTo
-import java.io.File
-import java.io.IOException
 import okhttp3.Response
 import okio.buffer
 import okio.sink
 import rx.Observable
 import uy.kohesive.injekt.injectLazy
+import java.io.File
+import java.io.IOException
 
 /**
  * Class used to create chapter cache

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

@@ -83,6 +83,11 @@ interface MangaQueries : DbProvider {
         .withPutResolver(MangaFlagsPutResolver())
         .prepare()
 
+    fun updateFlags(mangas: List<Manga>) = db.put()
+        .objects(mangas)
+        .withPutResolver(MangaFlagsPutResolver(true))
+        .prepare()
+
     fun updateLastUpdated(manga: Manga) = db.put()
         .`object`(manga)
         .withPutResolver(MangaLastUpdatedPutResolver())

+ 16 - 6
app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt

@@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.data.database.inTransactionReturn
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.database.tables.MangaTable
 
-class MangaFlagsPutResolver : PutResolver<Manga>() {
+class MangaFlagsPutResolver(private val updateAll: Boolean = false) : PutResolver<Manga>() {
 
     override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
         val updateQuery = mapToUpdateQuery(manga)
@@ -19,11 +19,21 @@ class MangaFlagsPutResolver : PutResolver<Manga>() {
         PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
     }
 
-    fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
-        .table(MangaTable.TABLE)
-        .where("${MangaTable.COL_ID} = ?")
-        .whereArgs(manga.id)
-        .build()
+    fun mapToUpdateQuery(manga: Manga): UpdateQuery {
+        val builder = UpdateQuery.builder()
+
+        return if (updateAll) {
+            builder
+                .table(MangaTable.TABLE)
+                .build()
+        } else {
+            builder
+                .table(MangaTable.TABLE)
+                .where("${MangaTable.COL_ID} = ?")
+                .whereArgs(manga.id)
+                .build()
+        }
+    }
 
     fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
         put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags)

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt

@@ -7,10 +7,10 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.source.SourceManager
-import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.flow.onEach
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.concurrent.TimeUnit
 
 /**
  * Cache where we dump the downloads directory from the filesystem. This class is needed because

+ 4 - 2
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt

@@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.util.lang.chop
 import eu.kanade.tachiyomi.util.system.notificationBuilder
 import eu.kanade.tachiyomi.util.system.notificationManager
-import java.util.regex.Pattern
 import uy.kohesive.injekt.injectLazy
+import java.util.regex.Pattern
 
 /**
  * DownloadNotifier is used to show notifications when downloading one or multiple chapters.
@@ -107,7 +107,9 @@ internal class DownloadNotifier(private val context: Context) {
             }
 
             val downloadingProgressText = context.getString(
-                R.string.chapter_downloading_progress, download.downloadedImages, download.pages!!.size
+                R.string.chapter_downloading_progress,
+                download.downloadedImages,
+                download.pages!!.size
             )
 
             if (preferences.hideNotificationContent()) {

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt

@@ -22,7 +22,6 @@ import eu.kanade.tachiyomi.util.lang.plusAssign
 import eu.kanade.tachiyomi.util.storage.DiskUtil
 import eu.kanade.tachiyomi.util.storage.saveTo
 import eu.kanade.tachiyomi.util.system.ImageUtil
-import java.io.File
 import kotlinx.coroutines.async
 import okhttp3.Response
 import rx.Observable
@@ -31,6 +30,7 @@ import rx.schedulers.Schedulers
 import rx.subscriptions.CompositeSubscription
 import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
+import java.io.File
 
 /**
  * This class is the one in charge of downloading chapters.

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/download/model/DownloadQueue.kt

@@ -5,9 +5,9 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.download.DownloadStore
 import eu.kanade.tachiyomi.source.model.Page
-import java.util.concurrent.CopyOnWriteArrayList
 import rx.Observable
 import rx.subjects.PublishSubject
+import java.util.concurrent.CopyOnWriteArrayList
 
 class DownloadQueue(
     private val store: DownloadStore,

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt

@@ -5,12 +5,12 @@ import android.util.Log
 import com.bumptech.glide.Priority
 import com.bumptech.glide.load.DataSource
 import com.bumptech.glide.load.data.DataFetcher
+import timber.log.Timber
 import java.io.File
 import java.io.FileInputStream
 import java.io.FileNotFoundException
 import java.io.IOException
 import java.io.InputStream
-import timber.log.Timber
 
 open class FileFetcher(private val filePath: String = "") : DataFetcher<InputStream> {
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaThumbnailModelLoader.kt

@@ -14,10 +14,10 @@ import eu.kanade.tachiyomi.network.NetworkHelper
 import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.source.online.HttpSource
 import eu.kanade.tachiyomi.util.isLocal
-import java.io.InputStream
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import uy.kohesive.injekt.injectLazy
+import java.io.InputStream
 
 /**
  * A class for loading a cover associated with a [Manga] that can be present in our own cache.

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt

@@ -14,9 +14,9 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
 import com.bumptech.glide.module.AppGlideModule
 import com.bumptech.glide.request.RequestOptions
 import eu.kanade.tachiyomi.network.NetworkHelper
-import java.io.InputStream
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.io.InputStream
 
 /**
  * Class used to update Glide module settings

+ 5 - 3
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt

@@ -9,9 +9,9 @@ import androidx.work.WorkManager
 import androidx.work.Worker
 import androidx.work.WorkerParameters
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
-import java.util.concurrent.TimeUnit
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.concurrent.TimeUnit
 
 class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
     Worker(context, workerParams) {
@@ -45,8 +45,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
                     .build()
 
                 val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
-                    interval.toLong(), TimeUnit.HOURS,
-                    10, TimeUnit.MINUTES
+                    interval.toLong(),
+                    TimeUnit.HOURS,
+                    10,
+                    TimeUnit.MINUTES
                 )
                     .addTag(TAG)
                     .setConstraints(constraints)

+ 10 - 5
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt

@@ -22,9 +22,9 @@ import eu.kanade.tachiyomi.util.lang.chop
 import eu.kanade.tachiyomi.util.system.notification
 import eu.kanade.tachiyomi.util.system.notificationBuilder
 import eu.kanade.tachiyomi.util.system.notificationManager
+import uy.kohesive.injekt.injectLazy
 import java.text.DecimalFormat
 import java.text.DecimalFormatSymbols
-import uy.kohesive.injekt.injectLazy
 
 class LibraryUpdateNotifier(private val context: Context) {
 
@@ -198,18 +198,23 @@ class LibraryUpdateNotifier(private val context: Context) {
 
             // Mark chapters as read action
             addAction(
-                R.drawable.ic_glasses_black_24dp, context.getString(R.string.action_mark_as_read),
+                R.drawable.ic_glasses_black_24dp,
+                context.getString(R.string.action_mark_as_read),
                 NotificationReceiver.markAsReadPendingBroadcast(
                     context,
-                    manga, chapters, Notifications.ID_NEW_CHAPTERS
+                    manga,
+                    chapters,
+                    Notifications.ID_NEW_CHAPTERS
                 )
             )
             // View chapters action
             addAction(
-                R.drawable.ic_book_24dp, context.getString(R.string.action_view_chapters),
+                R.drawable.ic_book_24dp,
+                context.getString(R.string.action_view_chapters),
                 NotificationReceiver.openChapterPendingActivity(
                     context,
-                    manga, Notifications.ID_NEW_CHAPTERS
+                    manga,
+                    Notifications.ID_NEW_CHAPTERS
                 )
             )
         }

+ 10 - 3
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt

@@ -6,6 +6,7 @@ import android.content.Intent
 import android.os.Build
 import android.os.IBinder
 import android.os.PowerManager
+import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.cache.CoverCache
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
 import eu.kanade.tachiyomi.data.database.models.Category
@@ -21,20 +22,21 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.util.chapter.NoChaptersException
 import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
 import eu.kanade.tachiyomi.util.prepUpdateCover
 import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
 import eu.kanade.tachiyomi.util.storage.getUriCompat
 import eu.kanade.tachiyomi.util.system.acquireWakeLock
 import eu.kanade.tachiyomi.util.system.isServiceRunning
-import java.io.File
-import java.util.concurrent.atomic.AtomicInteger
 import rx.Observable
 import rx.Subscription
 import rx.schedulers.Schedulers
 import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.io.File
+import java.util.concurrent.atomic.AtomicInteger
 
 /**
  * This class will take care of updating the chapters of the manga from the library. It can be
@@ -268,7 +270,12 @@ class LibraryUpdateService(
                 updateManga(manga)
                     // If there's any error, return empty update and continue.
                     .onErrorReturn {
-                        failedUpdates.add(Pair(manga, it.message))
+                        val errorMessage = if (it is NoChaptersException) {
+                            getString(R.string.no_chapters_error)
+                        } else {
+                            it.message
+                        }
+                        failedUpdates.add(Pair(manga, errorMessage))
                         Pair(emptyList(), emptyList())
                     }
                     // Filter out mangas without new chapters (or failed).

+ 10 - 6
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt

@@ -7,7 +7,6 @@ import android.content.Intent
 import android.net.Uri
 import android.os.Build
 import android.os.Handler
-import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.backup.BackupRestoreService
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
@@ -26,10 +25,11 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
 import eu.kanade.tachiyomi.util.storage.getUriCompat
 import eu.kanade.tachiyomi.util.system.notificationManager
 import eu.kanade.tachiyomi.util.system.toast
-import java.io.File
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import uy.kohesive.injekt.injectLazy
+import java.io.File
+import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
 
 /**
  * Global [BroadcastReceiver] that runs on UI thread
@@ -56,19 +56,22 @@ class NotificationReceiver : BroadcastReceiver() {
             // Launch share activity and dismiss notification
             ACTION_SHARE_IMAGE ->
                 shareImage(
-                    context, intent.getStringExtra(EXTRA_FILE_LOCATION),
+                    context,
+                    intent.getStringExtra(EXTRA_FILE_LOCATION),
                     intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
                 )
             // Delete image from path and dismiss notification
             ACTION_DELETE_IMAGE ->
                 deleteImage(
-                    context, intent.getStringExtra(EXTRA_FILE_LOCATION),
+                    context,
+                    intent.getStringExtra(EXTRA_FILE_LOCATION),
                     intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
                 )
             // Share backup file
             ACTION_SHARE_BACKUP ->
                 shareBackup(
-                    context, intent.getParcelableExtra(EXTRA_URI),
+                    context,
+                    intent.getParcelableExtra(EXTRA_URI),
                     intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
                 )
             ACTION_CANCEL_RESTORE -> cancelRestore(
@@ -80,7 +83,8 @@ class NotificationReceiver : BroadcastReceiver() {
             // Open reader activity
             ACTION_OPEN_CHAPTER -> {
                 openChapter(
-                    context, intent.getLongExtra(EXTRA_MANGA_ID, -1),
+                    context,
+                    intent.getLongExtra(EXTRA_MANGA_ID, -1),
                     intent.getLongExtra(EXTRA_CHAPTER_ID, -1)
                 )
             }

+ 18 - 9
app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt

@@ -82,53 +82,62 @@ object Notifications {
 
         listOf(
             NotificationChannel(
-                CHANNEL_COMMON, context.getString(R.string.channel_common),
+                CHANNEL_COMMON,
+                context.getString(R.string.channel_common),
                 NotificationManager.IMPORTANCE_LOW
             ),
             NotificationChannel(
-                CHANNEL_LIBRARY, context.getString(R.string.channel_library),
+                CHANNEL_LIBRARY,
+                context.getString(R.string.channel_library),
                 NotificationManager.IMPORTANCE_LOW
             ).apply {
                 setShowBadge(false)
             },
             NotificationChannel(
-                CHANNEL_DOWNLOADER_PROGRESS, context.getString(R.string.channel_progress),
+                CHANNEL_DOWNLOADER_PROGRESS,
+                context.getString(R.string.channel_progress),
                 NotificationManager.IMPORTANCE_LOW
             ).apply {
                 group = GROUP_DOWNLOADER
                 setShowBadge(false)
             },
             NotificationChannel(
-                CHANNEL_DOWNLOADER_COMPLETE, context.getString(R.string.channel_complete),
+                CHANNEL_DOWNLOADER_COMPLETE,
+                context.getString(R.string.channel_complete),
                 NotificationManager.IMPORTANCE_LOW
             ).apply {
                 group = GROUP_DOWNLOADER
                 setShowBadge(false)
             },
             NotificationChannel(
-                CHANNEL_DOWNLOADER_ERROR, context.getString(R.string.channel_errors),
+                CHANNEL_DOWNLOADER_ERROR,
+                context.getString(R.string.channel_errors),
                 NotificationManager.IMPORTANCE_LOW
             ).apply {
                 group = GROUP_DOWNLOADER
                 setShowBadge(false)
             },
             NotificationChannel(
-                CHANNEL_NEW_CHAPTERS, context.getString(R.string.channel_new_chapters),
+                CHANNEL_NEW_CHAPTERS,
+                context.getString(R.string.channel_new_chapters),
                 NotificationManager.IMPORTANCE_DEFAULT
             ),
             NotificationChannel(
-                CHANNEL_UPDATES_TO_EXTS, context.getString(R.string.channel_ext_updates),
+                CHANNEL_UPDATES_TO_EXTS,
+                context.getString(R.string.channel_ext_updates),
                 NotificationManager.IMPORTANCE_DEFAULT
             ),
             NotificationChannel(
-                CHANNEL_BACKUP_RESTORE_PROGRESS, context.getString(R.string.channel_progress),
+                CHANNEL_BACKUP_RESTORE_PROGRESS,
+                context.getString(R.string.channel_progress),
                 NotificationManager.IMPORTANCE_LOW
             ).apply {
                 group = GROUP_BACKUP_RESTORE
                 setShowBadge(false)
             },
             NotificationChannel(
-                CHANNEL_BACKUP_RESTORE_COMPLETE, context.getString(R.string.channel_complete),
+                CHANNEL_BACKUP_RESTORE_COMPLETE,
+                context.getString(R.string.channel_complete),
                 NotificationManager.IMPORTANCE_HIGH
             ).apply {
                 group = GROUP_BACKUP_RESTORE

+ 15 - 3
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt

@@ -109,11 +109,11 @@ object PreferenceKeys {
 
     const val downloadedOnly = "pref_downloaded_only"
 
-    const val filterDownloaded = "pref_filter_downloaded_key"
+    const val filterDownloaded = "pref_filter_library_downloaded"
 
-    const val filterUnread = "pref_filter_unread_key"
+    const val filterUnread = "pref_filter_library_unread"
 
-    const val filterCompleted = "pref_filter_completed_key"
+    const val filterCompleted = "pref_filter_library_completed"
 
     const val librarySortingMode = "library_sorting_mode"
 
@@ -165,6 +165,18 @@ object PreferenceKeys {
 
     const val enableDoh = "enable_doh"
 
+    const val defaultChapterFilterByRead = "default_chapter_filter_by_read"
+
+    const val defaultChapterFilterByDownloaded = "default_chapter_filter_by_downloaded"
+
+    const val defaultChapterFilterByBookmarked = "default_chapter_filter_by_bookmarked"
+
+    const val defaultChapterSortBySourceOrNumber = "default_chapter_sort_by_source_or_number" // and upload date
+
+    const val defaultChapterSortByAscendingOrDescending = "default_chapter_sort_by_ascending_or_descending"
+
+    const val defaultChapterDisplayByNameOrNumber = "default_chapter_display_by_name_or_number"
+
     fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
 
     fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"

+ 33 - 8
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -8,19 +8,21 @@ import androidx.preference.PreferenceManager
 import com.tfcporciuncula.flow.FlowSharedPreferences
 import com.tfcporciuncula.flow.Preference
 import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
-import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
+import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
 import eu.kanade.tachiyomi.data.preference.PreferenceValues.NsfwAllowance
 import eu.kanade.tachiyomi.data.track.TrackService
 import eu.kanade.tachiyomi.data.track.anilist.Anilist
+import eu.kanade.tachiyomi.widget.ExtendedNavigationView
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onEach
 import java.io.File
 import java.text.DateFormat
 import java.text.SimpleDateFormat
 import java.util.Locale
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.onEach
+import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
+import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
 
 @OptIn(ExperimentalCoroutinesApi::class)
 fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> {
@@ -209,11 +211,11 @@ class PreferencesHelper(val context: Context) {
 
     fun categoryTabs() = flowPrefs.getBoolean(Keys.categoryTabs, true)
 
-    fun filterDownloaded() = flowPrefs.getBoolean(Keys.filterDownloaded, false)
+    fun filterDownloaded() = flowPrefs.getInt(Keys.filterDownloaded, ExtendedNavigationView.Item.TriStateGroup.STATE_IGNORE)
 
-    fun filterUnread() = flowPrefs.getBoolean(Keys.filterUnread, false)
+    fun filterUnread() = flowPrefs.getInt(Keys.filterUnread, ExtendedNavigationView.Item.TriStateGroup.STATE_IGNORE)
 
-    fun filterCompleted() = flowPrefs.getBoolean(Keys.filterCompleted, false)
+    fun filterCompleted() = flowPrefs.getInt(Keys.filterCompleted, ExtendedNavigationView.Item.TriStateGroup.STATE_IGNORE)
 
     fun librarySortingMode() = flowPrefs.getInt(Keys.librarySortingMode, 0)
 
@@ -254,4 +256,27 @@ class PreferencesHelper(val context: Context) {
     fun lastSearchQuerySearchSettings() = prefs.getString("last_search_query", "")
     
     fun lastSearchQuerySearchSettings(query: String) = prefs.edit { putString("last_search_query", query) }
+
+    fun filterChapterByRead() = prefs.getInt(Keys.defaultChapterFilterByRead, Manga.SHOW_ALL)
+
+    fun filterChapterByDownloaded() = prefs.getInt(Keys.defaultChapterFilterByDownloaded, Manga.SHOW_ALL)
+
+    fun filterChapterByBookmarked() = prefs.getInt(Keys.defaultChapterFilterByBookmarked, Manga.SHOW_ALL)
+
+    fun sortChapterBySourceOrNumber() = prefs.getInt(Keys.defaultChapterSortBySourceOrNumber, Manga.SORTING_SOURCE)
+
+    fun displayChapterByNameOrNumber() = prefs.getInt(Keys.defaultChapterDisplayByNameOrNumber, Manga.DISPLAY_NAME)
+
+    fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.SORT_DESC)
+
+    fun setChapterSettingsDefault(manga: Manga) {
+        prefs.edit {
+            putInt(Keys.defaultChapterFilterByRead, manga.readFilter)
+            putInt(Keys.defaultChapterFilterByDownloaded, manga.downloadedFilter)
+            putInt(Keys.defaultChapterFilterByBookmarked, manga.bookmarkedFilter)
+            putInt(Keys.defaultChapterSortBySourceOrNumber, manga.sorting)
+            putInt(Keys.defaultChapterDisplayByNameOrNumber, manga.displayMode)
+            putInt(Keys.defaultChapterSortByAscendingOrDescending, if (manga.sortDescending()) Manga.SORT_DESC else Manga.SORT_ASC)
+        }
+    }
 }

+ 9 - 4
app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt

@@ -13,12 +13,12 @@ import com.google.gson.JsonParser
 import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import eu.kanade.tachiyomi.network.asObservableSuccess
-import java.util.Calendar
 import okhttp3.MediaType.Companion.toMediaTypeOrNull
 import okhttp3.OkHttpClient
 import okhttp3.Request
 import okhttp3.RequestBody.Companion.toRequestBody
 import rx.Observable
+import java.util.Calendar
 
 class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
 
@@ -271,9 +271,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
         }
 
         return ALManga(
-            struct["id"].asInt, struct["title"]["romaji"].asString, struct["coverImage"]["large"].asString,
-            struct["description"].nullString.orEmpty(), struct["type"].asString, struct["status"].nullString.orEmpty(),
-            date, struct["chapters"].nullInt ?: 0
+            struct["id"].asInt,
+            struct["title"]["romaji"].asString,
+            struct["coverImage"]["large"].asString,
+            struct["description"].nullString.orEmpty(),
+            struct["type"].asString,
+            struct["status"].nullString.orEmpty(),
+            date,
+            struct["chapters"].nullInt ?: 0
         )
     }
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistModels.kt

@@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
+import uy.kohesive.injekt.injectLazy
 import java.text.SimpleDateFormat
 import java.util.Locale
-import uy.kohesive.injekt.injectLazy
 
 data class ALManga(
     val media_id: Int,

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt

@@ -12,13 +12,13 @@ import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import eu.kanade.tachiyomi.network.POST
 import eu.kanade.tachiyomi.network.asObservableSuccess
-import java.net.URLEncoder
 import okhttp3.CacheControl
 import okhttp3.FormBody
 import okhttp3.OkHttpClient
 import okhttp3.Request
 import rx.Observable
 import uy.kohesive.injekt.injectLazy
+import java.net.URLEncoder
 
 class BangumiApi(private val client: OkHttpClient, interceptor: BangumiInterceptor) {
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt

@@ -7,10 +7,10 @@ import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.data.track.TrackService
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
-import java.text.DecimalFormat
 import rx.Completable
 import rx.Observable
 import uy.kohesive.injekt.injectLazy
+import java.text.DecimalFormat
 
 class Kitsu(private val context: Context, id: Int) : TrackService(id) {
 

+ 7 - 7
app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt

@@ -11,13 +11,6 @@ import eu.kanade.tachiyomi.network.asObservableSuccess
 import eu.kanade.tachiyomi.util.lang.toCalendar
 import eu.kanade.tachiyomi.util.selectInt
 import eu.kanade.tachiyomi.util.selectText
-import java.io.BufferedReader
-import java.io.InputStreamReader
-import java.text.SimpleDateFormat
-import java.util.Calendar
-import java.util.GregorianCalendar
-import java.util.Locale
-import java.util.zip.GZIPInputStream
 import okhttp3.FormBody
 import okhttp3.MediaType.Companion.toMediaTypeOrNull
 import okhttp3.OkHttpClient
@@ -30,6 +23,13 @@ import org.jsoup.nodes.Document
 import org.jsoup.nodes.Element
 import org.jsoup.parser.Parser
 import rx.Observable
+import java.io.BufferedReader
+import java.io.InputStreamReader
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.GregorianCalendar
+import java.util.Locale
+import java.util.zip.GZIPInputStream
 
 class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
 

+ 0 - 23
app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateChecker.kt

@@ -1,23 +0,0 @@
-package eu.kanade.tachiyomi.data.updater
-
-import eu.kanade.tachiyomi.BuildConfig
-import eu.kanade.tachiyomi.data.updater.devrepo.DevRepoUpdateChecker
-import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
-
-abstract class UpdateChecker {
-
-    companion object {
-        fun getUpdateChecker(): UpdateChecker {
-            return if (BuildConfig.DEBUG) {
-                DevRepoUpdateChecker()
-            } else {
-                GithubUpdateChecker()
-            }
-        }
-    }
-
-    /**
-     * Returns observable containing release information
-     */
-    abstract suspend fun checkForUpdate(): UpdateResult
-}

+ 7 - 4
app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterJob.kt

@@ -13,9 +13,10 @@ import androidx.work.Worker
 import androidx.work.WorkerParameters
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.notification.Notifications
+import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
 import eu.kanade.tachiyomi.util.system.notificationManager
-import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.runBlocking
+import java.util.concurrent.TimeUnit
 
 class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
     Worker(context, workerParams) {
@@ -23,7 +24,7 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
     override fun doWork(): Result {
         return runBlocking {
             try {
-                val result = UpdateChecker.getUpdateChecker().checkForUpdate()
+                val result = GithubUpdateChecker().checkForUpdate()
 
                 if (result is UpdateResult.NewUpdate<*>) {
                     val url = result.release.downloadLink
@@ -65,8 +66,10 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
                 .build()
 
             val request = PeriodicWorkRequestBuilder<UpdaterJob>(
-                3, TimeUnit.DAYS,
-                3, TimeUnit.HOURS
+                3,
+                TimeUnit.DAYS,
+                3,
+                TimeUnit.HOURS
             )
                 .addTag(TAG)
                 .setConstraints(constraints)

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt

@@ -20,9 +20,9 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
 import eu.kanade.tachiyomi.util.storage.saveTo
 import eu.kanade.tachiyomi.util.system.acquireWakeLock
 import eu.kanade.tachiyomi.util.system.isServiceRunning
-import java.io.File
 import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
+import java.io.File
 
 class UpdaterService : Service() {
 

+ 0 - 13
app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoRelease.kt

@@ -1,13 +0,0 @@
-package eu.kanade.tachiyomi.data.updater.devrepo
-
-import eu.kanade.tachiyomi.data.updater.Release
-
-class DevRepoRelease(override val info: String) : Release {
-
-    override val downloadLink: String
-        get() = LATEST_URL
-
-    companion object {
-        const val LATEST_URL = "https://tachiyomi.kanade.eu/latest"
-    }
-}

+ 0 - 41
app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoUpdateChecker.kt

@@ -1,41 +0,0 @@
-package eu.kanade.tachiyomi.data.updater.devrepo
-
-import eu.kanade.tachiyomi.BuildConfig
-import eu.kanade.tachiyomi.data.updater.UpdateChecker
-import eu.kanade.tachiyomi.data.updater.UpdateResult
-import eu.kanade.tachiyomi.network.GET
-import eu.kanade.tachiyomi.network.NetworkHelper
-import eu.kanade.tachiyomi.network.await
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import okhttp3.OkHttpClient
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-
-class DevRepoUpdateChecker : UpdateChecker() {
-
-    private val client: OkHttpClient by lazy {
-        Injekt.get<NetworkHelper>().client.newBuilder()
-            .followRedirects(false)
-            .build()
-    }
-
-    private val versionRegex: Regex by lazy {
-        Regex("tachiyomi-r(\\d+).apk")
-    }
-
-    override suspend fun checkForUpdate(): UpdateResult {
-        val response = withContext(Dispatchers.IO) {
-            client.newCall(GET(DevRepoRelease.LATEST_URL)).await()
-        }
-
-        // Get latest repo version number from header in format "Location: tachiyomi-r1512.apk"
-        val latestVersionNumber: String = versionRegex.find(response.header("Location")!!)!!.groupValues[1]
-
-        return if (latestVersionNumber.toInt() > BuildConfig.COMMIT_COUNT.toInt()) {
-            DevRepoUpdateResult.NewUpdate(DevRepoRelease("v$latestVersionNumber"))
-        } else {
-            DevRepoUpdateResult.NoNewUpdate()
-        }
-    }
-}

+ 0 - 9
app/src/main/java/eu/kanade/tachiyomi/data/updater/devrepo/DevRepoUpdateResult.kt

@@ -1,9 +0,0 @@
-package eu.kanade.tachiyomi.data.updater.devrepo
-
-import eu.kanade.tachiyomi.data.updater.UpdateResult
-
-sealed class DevRepoUpdateResult : UpdateResult() {
-
-    class NewUpdate(release: DevRepoRelease) : UpdateResult.NewUpdate<DevRepoRelease>(release)
-    class NoNewUpdate : UpdateResult.NoNewUpdate()
-}

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubRelease.kt

@@ -28,5 +28,5 @@ class GithubRelease(
      * Assets class containing download url.
      * @param downloadLink download url.
      */
-    inner class Assets(@SerializedName("browser_download_url") val downloadLink: String)
+    class Assets(@SerializedName("browser_download_url") val downloadLink: String)
 }

+ 4 - 3
app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubService.kt

@@ -4,11 +4,12 @@ import eu.kanade.tachiyomi.network.NetworkHelper
 import retrofit2.Retrofit
 import retrofit2.converter.gson.GsonConverterFactory
 import retrofit2.http.GET
+import retrofit2.http.Path
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 
 /**
- * Used to connect with the GitHub API.
+ * Used to connect with the GitHub API to get the latest release version from a repo.
  */
 interface GithubService {
 
@@ -24,6 +25,6 @@ interface GithubService {
         }
     }
 
-    @GET("/repos/inorichi/tachiyomi/releases/latest")
-    suspend fun getLatestVersion(): GithubRelease
+    @GET("/repos/{repo}/releases/latest")
+    suspend fun getLatestVersion(@Path("repo", encoded = true) repo: String): GithubRelease
 }

+ 26 - 6
app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateChecker.kt

@@ -1,23 +1,43 @@
 package eu.kanade.tachiyomi.data.updater.github
 
 import eu.kanade.tachiyomi.BuildConfig
-import eu.kanade.tachiyomi.data.updater.UpdateChecker
 import eu.kanade.tachiyomi.data.updater.UpdateResult
 
-class GithubUpdateChecker : UpdateChecker() {
+class GithubUpdateChecker {
 
     private val service: GithubService = GithubService.create()
 
-    override suspend fun checkForUpdate(): UpdateResult {
-        val release = service.getLatestVersion()
+    private val repo: String by lazy {
+        if (BuildConfig.DEBUG) {
+            "tachiyomiorg/android-app-preview"
+        } else {
+            "inorichi/tachiyomi"
+        }
+    }
 
-        val newVersion = release.version.replace("[^\\d.]".toRegex(), "")
+    suspend fun checkForUpdate(): UpdateResult {
+        val release = service.getLatestVersion(repo)
 
         // Check if latest version is different from current version
-        return if (newVersion != BuildConfig.VERSION_NAME) {
+        return if (isNewVersion(release.version)) {
             GithubUpdateResult.NewUpdate(release)
         } else {
             GithubUpdateResult.NoNewUpdate()
         }
     }
+
+    private fun isNewVersion(versionTag: String): Boolean {
+        // Removes prefixes like "r" or "v"
+        val newVersion = versionTag.replace("[^\\d.]".toRegex(), "")
+
+        return if (BuildConfig.DEBUG) {
+            // Preview builds: based on releases in "tachiyomiorg/android-app-preview" repo
+            // tagged as something like "r1234"
+            newVersion.toInt() > BuildConfig.COMMIT_COUNT.toInt()
+        } else {
+            // Release builds: based on releases in "inorichi/tachiyomi" repo
+            // tagged as something like "v0.1.2"
+            newVersion != BuildConfig.VERSION_NAME
+        }
+    }
 }

+ 5 - 3
app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt

@@ -16,10 +16,10 @@ import eu.kanade.tachiyomi.data.notification.Notifications
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
 import eu.kanade.tachiyomi.util.system.notification
-import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.coroutineScope
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.concurrent.TimeUnit
 
 class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParameters) :
     CoroutineWorker(context, workerParams) {
@@ -73,8 +73,10 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
                     .build()
 
                 val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>(
-                    12, TimeUnit.HOURS,
-                    1, TimeUnit.HOURS
+                    12,
+                    TimeUnit.HOURS,
+                    1,
+                    TimeUnit.HOURS
                 )
                     .addTag(TAG)
                     .setConstraints(constraints)

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt

@@ -9,10 +9,10 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.extension.model.Extension
 import eu.kanade.tachiyomi.extension.model.LoadResult
 import eu.kanade.tachiyomi.extension.util.ExtensionLoader
-import java.util.Date
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 import uy.kohesive.injekt.injectLazy
+import java.util.Date
 
 internal class ExtensionGithubApi {
 

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt

@@ -13,11 +13,11 @@ import com.jakewharton.rxrelay.PublishRelay
 import eu.kanade.tachiyomi.extension.model.Extension
 import eu.kanade.tachiyomi.extension.model.InstallStep
 import eu.kanade.tachiyomi.util.storage.getUriCompat
-import java.io.File
-import java.util.concurrent.TimeUnit
 import rx.Observable
 import rx.android.schedulers.AndroidSchedulers
 import timber.log.Timber
+import java.io.File
+import java.util.concurrent.TimeUnit
 
 /**
  * The installer which installs, updates and uninstalls the extensions.

+ 7 - 1
app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt

@@ -178,7 +178,13 @@ internal object ExtensionLoader {
         }
 
         val extension = Extension.Installed(
-            extName, pkgName, versionName, versionCode, lang, isNsfw, sources,
+            extName,
+            pkgName,
+            versionName,
+            versionCode,
+            lang,
+            isNsfw,
+            sources,
             isUnofficial = signatureHash != officialSignature
         )
         return LoadResult.Success(extension)

+ 3 - 3
app/src/main/java/eu/kanade/tachiyomi/network/CloudflareInterceptor.kt

@@ -16,15 +16,15 @@ import eu.kanade.tachiyomi.util.system.WebViewUtil
 import eu.kanade.tachiyomi.util.system.isOutdated
 import eu.kanade.tachiyomi.util.system.setDefaultSettings
 import eu.kanade.tachiyomi.util.system.toast
-import java.io.IOException
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 import okhttp3.Cookie
 import okhttp3.HttpUrl.Companion.toHttpUrl
 import okhttp3.Interceptor
 import okhttp3.Request
 import okhttp3.Response
 import uy.kohesive.injekt.injectLazy
+import java.io.IOException
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 
 class CloudflareInterceptor(private val context: Context) : Interceptor {
 

+ 3 - 3
app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt

@@ -3,15 +3,15 @@ package eu.kanade.tachiyomi.network
 import android.content.Context
 import eu.kanade.tachiyomi.BuildConfig
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
-import java.io.File
-import java.net.InetAddress
-import java.util.concurrent.TimeUnit
 import okhttp3.Cache
 import okhttp3.HttpUrl.Companion.toHttpUrl
 import okhttp3.OkHttpClient
 import okhttp3.dnsoverhttps.DnsOverHttps
 import okhttp3.logging.HttpLoggingInterceptor
 import uy.kohesive.injekt.injectLazy
+import java.io.File
+import java.net.InetAddress
+import java.util.concurrent.TimeUnit
 
 class NetworkHelper(context: Context) {
 

+ 19 - 17
app/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt

@@ -1,9 +1,5 @@
 package eu.kanade.tachiyomi.network
 
-import java.io.IOException
-import java.util.concurrent.atomic.AtomicBoolean
-import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
 import kotlinx.coroutines.suspendCancellableCoroutine
 import okhttp3.Call
 import okhttp3.Callback
@@ -13,6 +9,10 @@ import okhttp3.Response
 import rx.Observable
 import rx.Producer
 import rx.Subscription
+import java.io.IOException
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
 
 fun Call.asObservable(): Observable<Response> {
     return Observable.unsafeCreate { subscriber ->
@@ -54,22 +54,24 @@ fun Call.asObservable(): Observable<Response> {
 // Based on https://github.com/gildor/kotlin-coroutines-okhttp
 suspend fun Call.await(assertSuccess: Boolean = false): Response {
     return suspendCancellableCoroutine { continuation ->
-        enqueue(object : Callback {
-            override fun onResponse(call: Call, response: Response) {
-                if (assertSuccess && !response.isSuccessful) {
-                    continuation.resumeWithException(Exception("HTTP error ${response.code}"))
-                    return
-                }
+        enqueue(
+            object : Callback {
+                override fun onResponse(call: Call, response: Response) {
+                    if (assertSuccess && !response.isSuccessful) {
+                        continuation.resumeWithException(Exception("HTTP error ${response.code}"))
+                        return
+                    }
 
-                continuation.resume(response)
-            }
+                    continuation.resume(response)
+                }
 
-            override fun onFailure(call: Call, e: IOException) {
-                // Don't bother with resuming the continuation if it is already cancelled.
-                if (continuation.isCancelled) return
-                continuation.resumeWithException(e)
+                override fun onFailure(call: Call, e: IOException) {
+                    // Don't bother with resuming the continuation if it is already cancelled.
+                    if (continuation.isCancelled) return
+                    continuation.resumeWithException(e)
+                }
             }
-        })
+        )
 
         continuation.invokeOnCancellation {
             try {

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/network/ProgressResponseBody.kt

@@ -1,6 +1,5 @@
 package eu.kanade.tachiyomi.network
 
-import java.io.IOException
 import okhttp3.MediaType
 import okhttp3.ResponseBody
 import okio.Buffer
@@ -8,6 +7,7 @@ import okio.BufferedSource
 import okio.ForwardingSource
 import okio.Source
 import okio.buffer
+import java.io.IOException
 
 class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt

@@ -1,11 +1,11 @@
 package eu.kanade.tachiyomi.network
 
-import java.util.concurrent.TimeUnit.MINUTES
 import okhttp3.CacheControl
 import okhttp3.FormBody
 import okhttp3.Headers
 import okhttp3.Request
 import okhttp3.RequestBody
+import java.util.concurrent.TimeUnit.MINUTES
 
 private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build()
 private val DEFAULT_HEADERS = Headers.Builder().build()

+ 4 - 4
app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt

@@ -14,6 +14,10 @@ import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
 import eu.kanade.tachiyomi.util.storage.DiskUtil
 import eu.kanade.tachiyomi.util.storage.EpubFile
 import eu.kanade.tachiyomi.util.system.ImageUtil
+import junrar.Archive
+import junrar.rarfile.FileHeader
+import rx.Observable
+import timber.log.Timber
 import java.io.File
 import java.io.FileInputStream
 import java.io.InputStream
@@ -21,10 +25,6 @@ import java.util.Locale
 import java.util.concurrent.TimeUnit
 import java.util.zip.ZipEntry
 import java.util.zip.ZipFile
-import junrar.Archive
-import junrar.rarfile.FileHeader
-import rx.Observable
-import timber.log.Timber
 
 class LocalSource(private val context: Context) : CatalogueSource {
     companion object {

+ 3 - 3
app/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt

@@ -10,15 +10,15 @@ import eu.kanade.tachiyomi.source.model.MangasPage
 import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.source.model.SChapter
 import eu.kanade.tachiyomi.source.model.SManga
-import java.net.URI
-import java.net.URISyntaxException
-import java.security.MessageDigest
 import okhttp3.Headers
 import okhttp3.OkHttpClient
 import okhttp3.Request
 import okhttp3.Response
 import rx.Observable
 import uy.kohesive.injekt.injectLazy
+import java.net.URI
+import java.net.URISyntaxException
+import java.security.MessageDigest
 
 /**
  * A simple implementation for sources from a website.

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt

@@ -7,11 +7,11 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.lifecycle.lifecycleScope
 import androidx.viewbinding.ViewBinding
 import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
 import eu.kanade.tachiyomi.util.system.LocaleHelper
 import uy.kohesive.injekt.injectLazy
+import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
 
 abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
 

+ 32 - 28
app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt

@@ -22,27 +22,29 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
     lateinit var binding: VB
 
     init {
-        addLifecycleListener(object : LifecycleListener() {
-            override fun postCreateView(controller: Controller, view: View) {
-                onViewCreated(view)
+        addLifecycleListener(
+            object : LifecycleListener() {
+                override fun postCreateView(controller: Controller, view: View) {
+                    onViewCreated(view)
+                }
+
+                override fun preCreateView(controller: Controller) {
+                    Timber.d("Create view for ${controller.instance()}")
+                }
+
+                override fun preAttach(controller: Controller, view: View) {
+                    Timber.d("Attach view for ${controller.instance()}")
+                }
+
+                override fun preDetach(controller: Controller, view: View) {
+                    Timber.d("Detach view for ${controller.instance()}")
+                }
+
+                override fun preDestroyView(controller: Controller, view: View) {
+                    Timber.d("Destroy view for ${controller.instance()}")
+                }
             }
-
-            override fun preCreateView(controller: Controller) {
-                Timber.d("Create view for ${controller.instance()}")
-            }
-
-            override fun preAttach(controller: Controller, view: View) {
-                Timber.d("Attach view for ${controller.instance()}")
-            }
-
-            override fun preDetach(controller: Controller, view: View) {
-                Timber.d("Detach view for ${controller.instance()}")
-            }
-
-            override fun preDestroyView(controller: Controller, view: View) {
-                Timber.d("Destroy view for ${controller.instance()}")
-            }
-        })
+        )
     }
 
     override val containerView: View?
@@ -98,17 +100,19 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
     var expandActionViewFromInteraction = false
 
     fun MenuItem.fixExpand(onExpand: ((MenuItem) -> Boolean)? = null, onCollapse: ((MenuItem) -> Boolean)? = null) {
-        setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
-            override fun onMenuItemActionExpand(item: MenuItem): Boolean {
-                return onExpand?.invoke(item) ?: true
-            }
+        setOnActionExpandListener(
+            object : MenuItem.OnActionExpandListener {
+                override fun onMenuItemActionExpand(item: MenuItem): Boolean {
+                    return onExpand?.invoke(item) ?: true
+                }
 
-            override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
-                activity?.invalidateOptionsMenu()
+                override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
+                    activity?.invalidateOptionsMenu()
 
-                return onCollapse?.invoke(item) ?: true
+                    return onCollapse?.invoke(item) ?: true
+                }
             }
-        })
+        )
 
         if (expandActionViewFromInteraction) {
             expandActionViewFromInteraction = false

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionPresenter.kt

@@ -10,12 +10,12 @@ import eu.kanade.tachiyomi.extension.model.Extension
 import eu.kanade.tachiyomi.extension.model.InstallStep
 import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 import eu.kanade.tachiyomi.util.system.LocaleHelper
-import java.util.concurrent.TimeUnit
 import rx.Observable
 import rx.Subscription
 import rx.android.schedulers.AndroidSchedulers
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.concurrent.TimeUnit
 
 private typealias ExtensionTuple =
     Triple<List<Extension.Installed>, List<Extension.Untrusted>, List<Extension.Available>>

+ 1 - 4
app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsController.kt

@@ -20,8 +20,6 @@ import androidx.preference.PreferenceManager
 import androidx.preference.PreferenceScreen
 import androidx.preference.SwitchPreferenceCompat
 import androidx.recyclerview.widget.ConcatAdapter
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
 import androidx.recyclerview.widget.LinearLayoutManager
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
@@ -92,7 +90,6 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
             ExtensionDetailsHeaderAdapter(presenter),
             initPreferencesAdapter(context, extension)
         )
-        binding.extensionPrefsRecycler.addItemDecoration(DividerItemDecoration(context, VERTICAL))
     }
 
     private fun initPreferencesAdapter(context: Context, extension: Extension.Installed): PreferenceGroupAdapter {
@@ -112,7 +109,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
                 .forEach {
                     val preferenceBlock = {
                         it.value
-                            .sortedWith(compareBy({ !it.isEnabled() }, { it.name }))
+                            .sortedWith(compareBy({ !it.isEnabled() }, { it.name.toLowerCase() }))
                             .forEach { source ->
                                 val sourcePrefs = mutableListOf<Preference>()
 

+ 0 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt

@@ -19,8 +19,6 @@ import androidx.preference.Preference
 import androidx.preference.PreferenceGroupAdapter
 import androidx.preference.PreferenceManager
 import androidx.preference.PreferenceScreen
-import androidx.recyclerview.widget.DividerItemDecoration
-import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
 import androidx.recyclerview.widget.LinearLayoutManager
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
@@ -86,7 +84,6 @@ class SourcePreferencesController(bundle: Bundle? = null) :
 
         binding.recycler.layoutManager = LinearLayoutManager(context)
         binding.recycler.adapter = PreferenceGroupAdapter(screen)
-        binding.recycler.addItemDecoration(DividerItemDecoration(context, VERTICAL))
     }
 
     override fun onDestroyView(view: View) {

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt

@@ -13,10 +13,10 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchCardItem
 import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItem
 import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter
 import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
-import java.util.Date
 import rx.Observable
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
+import java.util.Date
 
 class SearchPresenter(
     initialQuery: String? = "",

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesPresenter.kt

@@ -29,7 +29,7 @@ class MigrationSourcesPresenter(
         val header = SelectionHeader()
         return library.map { it.source }.toSet()
             .mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null }
-            .sortedBy { it.name }
+            .sortedBy { it.name.toLowerCase() }
             .map { SourceItem(it, header) }
     }
 }

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceFilterController.kt

@@ -16,9 +16,9 @@ import eu.kanade.tachiyomi.util.preference.onChange
 import eu.kanade.tachiyomi.util.preference.switchPreferenceCategory
 import eu.kanade.tachiyomi.util.preference.titleRes
 import eu.kanade.tachiyomi.util.system.LocaleHelper
-import java.util.TreeMap
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.TreeMap
 
 class SourceFilterController : SettingsController() {
 
@@ -42,7 +42,7 @@ class SourceFilterController : SettingsController() {
         )
 
         orderedLangs.forEach { lang ->
-            val sources = sourcesByLang[lang].orEmpty().sortedBy { it.name }
+            val sources = sourcesByLang[lang].orEmpty().sortedBy { it.name.toLowerCase() }
 
             // Create a preference group and set initial state and change listener
             switchPreferenceCategory {

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcePresenter.kt

@@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource
 import eu.kanade.tachiyomi.source.LocalSource
 import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
-import java.util.TreeMap
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
@@ -21,6 +20,7 @@ import rx.Observable
 import rx.Subscription
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.TreeMap
 
 /**
  * Presenter of [SourceController]
@@ -128,7 +128,7 @@ class SourcePresenter(
         return sourceManager.getCatalogueSources()
             .filter { it.lang in languages }
             .filterNot { it.id.toString() in disabledSourceIds }
-            .sortedBy { "(${it.lang}) ${it.name}" } +
+            .sortedBy { "(${it.lang}) ${it.name.toLowerCase()}" } +
             sourceManager.get(LocalSource.ID) as LocalSource
     }
 

+ 0 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt

@@ -10,7 +10,6 @@ import android.view.View
 import android.view.ViewGroup
 import androidx.appcompat.widget.SearchView
 import androidx.core.view.isVisible
-import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
@@ -216,7 +215,6 @@ open class BrowseSourceController(bundle: Bundle) :
                 id = R.id.recycler
                 layoutManager = LinearLayoutManager(context)
                 layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
-                addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
             }
         } else {
             (binding.catalogueView.inflate(R.layout.source_recycler_autofit) as AutofitRecyclerView).apply {

+ 4 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt

@@ -28,9 +28,8 @@ import eu.kanade.tachiyomi.ui.browse.source.filter.TextItem
 import eu.kanade.tachiyomi.ui.browse.source.filter.TextSectionItem
 import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateItem
 import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem
+import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
 import eu.kanade.tachiyomi.util.removeCovers
-import java.util.Date
-import kotlinx.coroutines.flow.subscribe
 import rx.Observable
 import rx.Subscription
 import rx.android.schedulers.AndroidSchedulers
@@ -39,6 +38,7 @@ import rx.subjects.PublishSubject
 import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.Date
 
 /**
  * Presenter of [BrowseSourceController].
@@ -268,6 +268,8 @@ open class BrowseSourcePresenter(
 
         if (!manga.favorite) {
             manga.removeCovers(coverCache)
+        } else {
+            ChapterSettingsHelper.applySettingDefaults(manga)
         }
 
         db.insertManga(manga).executeAsBlocking()

+ 7 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceItem.kt

@@ -38,10 +38,13 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
                 val coverHeight = parent.itemWidth / 3 * 4
                 view.apply {
                     card.layoutParams = FrameLayout.LayoutParams(
-                        MATCH_PARENT, coverHeight
+                        MATCH_PARENT,
+                        coverHeight
                     )
                     gradient.layoutParams = FrameLayout.LayoutParams(
-                        MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM
+                        MATCH_PARENT,
+                        coverHeight / 2,
+                        Gravity.BOTTOM
                     )
                 }
                 SourceGridHolder(view, adapter)
@@ -51,7 +54,8 @@ class SourceItem(val manga: Manga, private val displayMode: Preference<DisplayMo
                 val coverHeight = parent.itemWidth / 3 * 4
                 view.apply {
                     card.layoutParams = ConstraintLayout.LayoutParams(
-                        MATCH_PARENT, coverHeight
+                        MATCH_PARENT,
+                        coverHeight
                     )
                 }
                 SourceComfortableGridHolder(view, adapter)

+ 2 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/filter/SelectItem.kt

@@ -30,7 +30,8 @@ open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<Selec
         spinner.prompt = filter.name
         spinner.adapter = ArrayAdapter<Any>(
             holder.itemView.context,
-            android.R.layout.simple_spinner_item, filter.values
+            android.R.layout.simple_spinner_item,
+            filter.values
         ).apply {
             setDropDownViewResource(R.layout.common_spinner_item)
         }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/filter/TriStateItem.kt

@@ -9,10 +9,10 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
 import eu.davidea.flexibleadapter.items.IFlexible
 import eu.davidea.viewholders.FlexibleViewHolder
-import eu.kanade.tachiyomi.R as TR
 import eu.kanade.tachiyomi.source.model.Filter
 import eu.kanade.tachiyomi.util.system.dpToPx
 import eu.kanade.tachiyomi.util.system.getResourceColor
+import eu.kanade.tachiyomi.R as TR
 
 open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem<TriStateItem.Holder>() {
 

+ 12 - 10
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt

@@ -108,17 +108,19 @@ open class GlobalSearchController(
         val searchView = searchItem.actionView as SearchView
         searchView.maxWidth = Int.MAX_VALUE
 
-        searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
-            override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
-                searchView.onActionViewExpanded() // Required to show the query in the view
-                searchView.setQuery(presenter.query, false)
-                return true
+        searchItem.setOnActionExpandListener(
+            object : MenuItem.OnActionExpandListener {
+                override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
+                    searchView.onActionViewExpanded() // Required to show the query in the view
+                    searchView.setQuery(presenter.query, false)
+                    return true
+                }
+
+                override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
+                    return true
+                }
             }
-
-            override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
-                return true
-            }
-        })
+        )
 
         searchView.queryTextEvents()
             .filterIsInstance<QueryTextEvent.QuerySubmitted>()

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchPresenter.kt

@@ -108,7 +108,7 @@ open class GlobalSearchPresenter(
         return sourceManager.getCatalogueSources()
             .filter { it.lang in languages }
             .filterNot { it.id.toString() in disabledSourceIds }
-            .sortedWith(compareBy({ it.id.toString() !in pinnedSourceIds }, { "${it.name} (${it.lang})" }))
+            .sortedWith(compareBy({ it.id.toString() !in pinnedSourceIds }, { "${it.name.toLowerCase()} (${it.lang})" }))
     }
 
     private fun getSourcesToQuery(): List<CatalogueSource> {
@@ -188,7 +188,7 @@ open class GlobalSearchPresenter(
                             { it.results.isNullOrEmpty() },
                             // Same as initial sort, i.e. pinned first then alphabetically
                             { it.source.id.toString() !in pinnedSourceIds },
-                            { "${it.source.name} (${it.source.lang})" }
+                            { "${it.source.name.toLowerCase()} (${it.source.lang})" }
                         )
                     )
             }

+ 5 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt

@@ -198,8 +198,11 @@ class CategoryController :
             R.id.action_delete -> {
                 undoHelper = UndoHelper(adapter, this)
                 undoHelper?.start(
-                    adapter.selectedPositions, activity!!.root_coordinator,
-                    R.string.snack_categories_deleted, R.string.action_undo, 3000
+                    adapter.selectedPositions,
+                    activity!!.root_coordinator,
+                    R.string.snack_categories_deleted,
+                    R.string.action_undo,
+                    3000
                 )
 
                 mode.finish()

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt

@@ -18,13 +18,13 @@ import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.ui.base.controller.FabController
 import eu.kanade.tachiyomi.ui.base.controller.NucleusController
 import eu.kanade.tachiyomi.util.view.shrinkOnScroll
-import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import reactivecircus.flowbinding.android.view.clicks
 import rx.Observable
 import rx.Subscription
 import rx.android.schedulers.AndroidSchedulers
+import java.util.concurrent.TimeUnit
 
 /**
  * Controller that shows the currently active downloads.

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt

@@ -183,7 +183,7 @@ class LibraryController(
             createActionModeIfNeeded()
         }
 
-        settingsSheet = LibrarySettingsSheet(activity!!) { group ->
+        settingsSheet = LibrarySettingsSheet(router) { group ->
             when (group) {
                 is LibrarySettingsSheet.Filter.FilterGroup -> onFilterChanged()
                 is LibrarySettingsSheet.Sort.SortGroup -> onSortChanged()

+ 5 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt

@@ -45,7 +45,9 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe
                 view.apply {
                     card.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, coverHeight)
                     gradient.layoutParams = FrameLayout.LayoutParams(
-                        MATCH_PARENT, coverHeight / 2, Gravity.BOTTOM
+                        MATCH_PARENT,
+                        coverHeight / 2,
+                        Gravity.BOTTOM
                     )
                 }
                 LibraryCompactGridHolder(view, adapter)
@@ -55,7 +57,8 @@ class LibraryItem(val manga: LibraryManga, private val libraryDisplayMode: Prefe
                 val coverHeight = parent.itemWidth / 3 * 4
                 view.apply {
                     card.layoutParams = ConstraintLayout.LayoutParams(
-                        MATCH_PARENT, coverHeight
+                        MATCH_PARENT,
+                        coverHeight
                     )
                 }
                 LibraryComfortableGridHolder(view, adapter)

+ 36 - 23
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt

@@ -19,14 +19,16 @@ import eu.kanade.tachiyomi.util.lang.combineLatest
 import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
 import eu.kanade.tachiyomi.util.lang.launchIO
 import eu.kanade.tachiyomi.util.removeCovers
-import java.util.Collections
-import java.util.Comparator
+import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_IGNORE
+import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_INCLUDE
 import rx.Observable
 import rx.Subscription
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.Collections
+import java.util.Comparator
 
 /**
  * Class containing library information.
@@ -110,34 +112,45 @@ class LibraryPresenter(
      * @param map the map to filter.
      */
     private fun applyFilters(map: LibraryMap): LibraryMap {
-        val filterDownloaded = preferences.downloadedOnly().get() || preferences.filterDownloaded().get()
+        val downloadedOnly = preferences.downloadedOnly().get()
+        val filterDownloaded = preferences.filterDownloaded().get()
         val filterUnread = preferences.filterUnread().get()
         val filterCompleted = preferences.filterCompleted().get()
 
-        val filterFn: (LibraryItem) -> Boolean = f@{ item ->
-            // Filter when there isn't unread chapters.
-            if (filterUnread && item.manga.unread == 0) {
-                return@f false
-            }
+        val filterFnUnread: (LibraryItem) -> Boolean = unread@{ item ->
+            if (filterUnread == STATE_IGNORE) return@unread true
+            val isUnread = item.manga.unread != 0
 
-            if (filterCompleted && item.manga.status != SManga.COMPLETED) {
-                return@f false
-            }
+            return@unread if (filterUnread == STATE_INCLUDE) isUnread
+            else !isUnread
+        }
 
-            // Filter when there are no downloads.
-            if (filterDownloaded) {
-                // Local manga are always downloaded
-                if (item.manga.isLocal()) {
-                    return@f true
-                }
-                // Don't bother with directory checking if download count has been set.
-                if (item.downloadCount != -1) {
-                    return@f item.downloadCount > 0
-                }
+        val filterFnCompleted: (LibraryItem) -> Boolean = completed@{ item ->
+            if (filterCompleted == STATE_IGNORE) return@completed true
+            val isCompleted = item.manga.status == SManga.COMPLETED
+
+            return@completed if (filterCompleted == STATE_INCLUDE) isCompleted
+            else !isCompleted
+        }
 
-                return@f downloadManager.getDownloadCount(item.manga) > 0
+        val filterFnDownloaded: (LibraryItem) -> Boolean = downloaded@{ item ->
+            if (!downloadedOnly && filterDownloaded == STATE_IGNORE) return@downloaded true
+            val isDownloaded = when {
+                item.manga.isLocal() -> true
+                item.downloadCount != -1 -> item.downloadCount > 0
+                else -> downloadManager.getDownloadCount(item.manga) > 0
             }
-            true
+
+            return@downloaded if (downloadedOnly || filterDownloaded == STATE_INCLUDE) isDownloaded
+            else !isDownloaded
+        }
+
+        val filterFn: (LibraryItem) -> Boolean = filter@{ item ->
+            return@filter !(
+                !filterFnUnread(item) ||
+                    !filterFnCompleted(item) ||
+                    !filterFnDownloaded(item)
+                )
         }
 
         return map.mapValues { entry -> entry.value.filter(filterFn) }

+ 32 - 19
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt

@@ -1,33 +1,36 @@
 package eu.kanade.tachiyomi.ui.library
 
-import android.app.Activity
 import android.content.Context
 import android.util.AttributeSet
 import android.view.View
+import com.bluelinelabs.conductor.Router
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.widget.ExtendedNavigationView
+import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_EXCLUDE
+import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_IGNORE
+import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.Companion.STATE_INCLUDE
 import eu.kanade.tachiyomi.widget.TabbedBottomSheetDialog
 import uy.kohesive.injekt.injectLazy
 
 class LibrarySettingsSheet(
-    activity: Activity,
+    router: Router,
     onGroupClickListener: (ExtendedNavigationView.Group) -> Unit
-) : TabbedBottomSheetDialog(activity) {
+) : TabbedBottomSheetDialog(router) {
 
     val filters: Filter
     private val sort: Sort
     private val display: Display
 
     init {
-        filters = Filter(activity)
+        filters = Filter(router.activity!!)
         filters.onGroupClicked = onGroupClickListener
 
-        sort = Sort(activity)
+        sort = Sort(router.activity!!)
         sort.onGroupClicked = onGroupClickListener
 
-        display = Display(activity)
+        display = Display(router.activity!!)
         display.onGroupClicked = onGroupClickListener
     }
 
@@ -59,33 +62,43 @@ class LibrarySettingsSheet(
          * Returns true if there's at least one filter from [FilterGroup] active.
          */
         fun hasActiveFilters(): Boolean {
-            return filterGroup.items.any { it.checked }
+            return filterGroup.items.any { it.state != Item.TriStateGroup.STATE_IGNORE }
         }
 
         inner class FilterGroup : Group {
 
-            private val downloaded = Item.CheckboxGroup(R.string.action_filter_downloaded, this)
-            private val unread = Item.CheckboxGroup(R.string.action_filter_unread, this)
-            private val completed = Item.CheckboxGroup(R.string.completed, this)
+            private val downloaded = Item.TriStateGroup(R.string.action_filter_downloaded, this)
+            private val unread = Item.TriStateGroup(R.string.action_filter_unread, this)
+            private val completed = Item.TriStateGroup(R.string.completed, this)
 
             override val header = null
             override val items = listOf(downloaded, unread, completed)
             override val footer = null
 
             override fun initModels() {
-                downloaded.checked = preferences.downloadedOnly().get() || preferences.filterDownloaded().get()
-                downloaded.enabled = !preferences.downloadedOnly().get()
-                unread.checked = preferences.filterUnread().get()
-                completed.checked = preferences.filterCompleted().get()
+                if (preferences.downloadedOnly().get()) {
+                    downloaded.state = STATE_INCLUDE
+                    downloaded.enabled = false
+                } else {
+                    downloaded.state = preferences.filterDownloaded().get()
+                }
+                unread.state = preferences.filterUnread().get()
+                completed.state = preferences.filterCompleted().get()
             }
 
             override fun onItemClicked(item: Item) {
-                item as Item.CheckboxGroup
-                item.checked = !item.checked
+                item as Item.TriStateGroup
+                val newState = when (item.state) {
+                    STATE_IGNORE -> STATE_INCLUDE
+                    STATE_INCLUDE -> STATE_EXCLUDE
+                    STATE_EXCLUDE -> STATE_IGNORE
+                    else -> throw Exception("Unknown State")
+                }
+                item.state = newState
                 when (item) {
-                    downloaded -> preferences.filterDownloaded().set(item.checked)
-                    unread -> preferences.filterUnread().set(item.checked)
-                    completed -> preferences.filterCompleted().set(item.checked)
+                    downloaded -> preferences.filterDownloaded().set(newState)
+                    unread -> preferences.filterUnread().set(newState)
+                    completed -> preferences.filterCompleted().set(newState)
                 }
 
                 adapter.notifyItemChanged(item)

+ 22 - 20
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

@@ -44,13 +44,13 @@ import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
 import eu.kanade.tachiyomi.util.lang.launchIO
 import eu.kanade.tachiyomi.util.lang.launchUI
 import eu.kanade.tachiyomi.util.system.toast
-import java.util.Date
-import java.util.concurrent.TimeUnit
 import kotlinx.android.synthetic.main.main_activity.appbar
 import kotlinx.android.synthetic.main.main_activity.tabs
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.launchIn
 import timber.log.Timber
+import java.util.Date
+import java.util.concurrent.TimeUnit
 
 class MainActivity : BaseActivity<MainActivityBinding>() {
 
@@ -126,26 +126,28 @@ class MainActivity : BaseActivity<MainActivityBinding>() {
             onBackPressed()
         }
 
-        router.addChangeListener(object : ControllerChangeHandler.ControllerChangeListener {
-            override fun onChangeStarted(
-                to: Controller?,
-                from: Controller?,
-                isPush: Boolean,
-                container: ViewGroup,
-                handler: ControllerChangeHandler
-            ) {
-                syncActivityViewWithController(to, from)
-            }
+        router.addChangeListener(
+            object : ControllerChangeHandler.ControllerChangeListener {
+                override fun onChangeStarted(
+                    to: Controller?,
+                    from: Controller?,
+                    isPush: Boolean,
+                    container: ViewGroup,
+                    handler: ControllerChangeHandler
+                ) {
+                    syncActivityViewWithController(to, from)
+                }
 
-            override fun onChangeCompleted(
-                to: Controller?,
-                from: Controller?,
-                isPush: Boolean,
-                container: ViewGroup,
-                handler: ControllerChangeHandler
-            ) {
+                override fun onChangeCompleted(
+                    to: Controller?,
+                    from: Controller?,
+                    isPush: Boolean,
+                    container: ViewGroup,
+                    handler: ControllerChangeHandler
+                ) {
+                }
             }
-        })
+        )
 
         syncActivityViewWithController(router.backstack.lastOrNull()?.controller())
 

+ 15 - 10
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt

@@ -52,7 +52,6 @@ import eu.kanade.tachiyomi.ui.library.ChangeMangaCoverDialog
 import eu.kanade.tachiyomi.ui.library.LibraryController
 import eu.kanade.tachiyomi.ui.main.MainActivity
 import eu.kanade.tachiyomi.ui.main.offsetAppbarHeight
-import eu.kanade.tachiyomi.ui.manga.chapter.ChapterDividerItemDecoration
 import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
 import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersAdapter
 import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersSettingsSheet
@@ -65,13 +64,13 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity
 import eu.kanade.tachiyomi.ui.recent.history.HistoryController
 import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController
 import eu.kanade.tachiyomi.ui.webview.WebViewActivity
+import eu.kanade.tachiyomi.util.chapter.NoChaptersException
 import eu.kanade.tachiyomi.util.hasCustomCover
 import eu.kanade.tachiyomi.util.system.getResourceColor
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.view.getCoordinates
 import eu.kanade.tachiyomi.util.view.shrinkOnScroll
 import eu.kanade.tachiyomi.util.view.snack
-import kotlin.math.min
 import kotlinx.android.synthetic.main.main_activity.root_coordinator
 import kotlinx.android.synthetic.main.main_activity.toolbar
 import kotlinx.coroutines.flow.launchIn
@@ -83,6 +82,7 @@ import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import uy.kohesive.injekt.injectLazy
+import kotlin.math.min
 
 class MangaController :
     NucleusController<MangaControllerBinding, MangaPresenter>,
@@ -207,7 +207,6 @@ class MangaController :
 
         binding.recycler.adapter = ConcatAdapter(mangaInfoAdapter, chaptersHeaderAdapter, chaptersAdapter)
         binding.recycler.layoutManager = LinearLayoutManager(view.context)
-        binding.recycler.addItemDecoration(ChapterDividerItemDecoration(view.context))
         binding.recycler.setHasFixedSize(true)
         chaptersAdapter?.fastScroller = binding.fastScroller
 
@@ -238,7 +237,7 @@ class MangaController :
 
         binding.actionToolbar.offsetAppbarHeight(activity!!)
 
-        settingsSheet = ChaptersSettingsSheet(activity!!, presenter) { group ->
+        settingsSheet = ChaptersSettingsSheet(router, presenter) { group ->
             if (group is ChaptersSettingsSheet.Filter.FilterGroup) {
                 updateFilterIconState()
                 chaptersAdapter?.notifyDataSetChanged()
@@ -292,10 +291,10 @@ class MangaController :
                     // Get coordinates and start animation
                     actionFab?.getCoordinates()?.let { coordinates ->
                         if (!binding.revealView.showRevealEffect(
-                            coordinates.x,
-                            coordinates.y,
-                            revealAnimationListener
-                        )
+                                coordinates.x,
+                                coordinates.y,
+                                revealAnimationListener
+                            )
                         ) {
                             openChapter(item.chapter)
                         }
@@ -343,7 +342,8 @@ class MangaController :
     }
 
     override fun onPrepareOptionsMenu(menu: Menu) {
-        // Hide download options for local manga
+        // Hide options for local manga
+        menu.findItem(R.id.action_share).isVisible = !isLocalSource
         menu.findItem(R.id.download_group).isVisible = !isLocalSource
 
         // Hide options for non-library manga
@@ -354,6 +354,7 @@ class MangaController :
 
     override fun onOptionsItemSelected(item: MenuItem): Boolean {
         when (item.itemId) {
+            R.id.action_share -> shareManga()
             R.id.download_next, R.id.download_next_5, R.id.download_next_10,
             R.id.download_custom, R.id.download_unread, R.id.download_all
             -> downloadChapters(item.itemId)
@@ -694,7 +695,11 @@ class MangaController :
     fun onFetchChaptersError(error: Throwable) {
         isRefreshingChapters = false
         updateRefreshing()
-        activity?.toast(error.message)
+        if (error is NoChaptersException) {
+            activity?.toast(activity?.getString(R.string.no_chapters_error))
+        } else {
+            activity?.toast(error.message)
+        }
     }
 
     fun onChapterStatusChange(download: Download) {

+ 6 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt

@@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.source.LocalSource
 import eu.kanade.tachiyomi.source.Source
 import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
+import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
 import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
 import eu.kanade.tachiyomi.util.isLocal
 import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
@@ -26,7 +27,6 @@ import eu.kanade.tachiyomi.util.prepUpdateCover
 import eu.kanade.tachiyomi.util.removeCovers
 import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
 import eu.kanade.tachiyomi.util.updateCoverLastModified
-import java.util.Date
 import rx.Observable
 import rx.Subscription
 import rx.android.schedulers.AndroidSchedulers
@@ -34,6 +34,7 @@ import rx.schedulers.Schedulers
 import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.Date
 
 class MangaPresenter(
     val manga: Manga,
@@ -82,6 +83,10 @@ class MangaPresenter(
     override fun onCreate(savedState: Bundle?) {
         super.onCreate(savedState)
 
+        if (!manga.favorite) {
+            ChapterSettingsHelper.applySettingDefaults(manga)
+        }
+
         // Manga info - start
 
         getMangaObservable()

+ 0 - 59
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterDividerItemDecoration.kt

@@ -1,59 +0,0 @@
-package eu.kanade.tachiyomi.ui.manga.chapter
-
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Rect
-import android.graphics.drawable.Drawable
-import android.view.View
-import androidx.core.view.forEach
-import androidx.core.view.marginBottom
-import androidx.recyclerview.widget.RecyclerView
-
-/**
- * Mimics a DividerItemDecoration that doesn't draw between the first two items.
- *
- * Used in MangaController since the manga info header and chapters header are the first two
- * items in the list using a ConcatAdapter.
- */
-class ChapterDividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
-
-    private val divider: Drawable
-
-    init {
-        val a = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider))
-        divider = a.getDrawable(0)!!
-        a.recycle()
-    }
-
-    override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
-        if (parent.layoutManager == null) {
-            return
-        }
-
-        canvas.save()
-        parent.forEach {
-            val top = it.bottom + it.marginBottom
-            val bottom = top + divider.intrinsicHeight
-            val left = parent.paddingStart
-            val right = parent.width - parent.paddingEnd
-            divider.setBounds(left, top, right, bottom)
-            divider.draw(canvas)
-        }
-        canvas.restore()
-    }
-
-    override fun getItemOffsets(
-        outRect: Rect,
-        view: View,
-        parent: RecyclerView,
-        state: RecyclerView.State
-    ) {
-        val position = parent.getChildAdapterPosition(view)
-
-        if (position == 0) {
-            outRect.setEmpty()
-        } else {
-            outRect.set(0, 0, 0, divider.intrinsicHeight)
-        }
-    }
-}

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt

@@ -9,11 +9,11 @@ import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.download.model.Download
 import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
-import java.util.Date
 import kotlinx.android.synthetic.main.chapters_item.bookmark_icon
 import kotlinx.android.synthetic.main.chapters_item.chapter_description
 import kotlinx.android.synthetic.main.chapters_item.chapter_title
 import kotlinx.android.synthetic.main.chapters_item.download_text
+import java.util.Date
 
 class ChapterHolder(
     view: View,

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt

@@ -6,10 +6,10 @@ import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.ui.manga.MangaController
 import eu.kanade.tachiyomi.util.system.getResourceColor
+import uy.kohesive.injekt.injectLazy
 import java.text.DateFormat
 import java.text.DecimalFormat
 import java.text.DecimalFormatSymbols
-import uy.kohesive.injekt.injectLazy
 
 class ChaptersAdapter(
     controller: MangaController,

+ 28 - 6
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSettingsSheet.kt

@@ -1,34 +1,39 @@
 package eu.kanade.tachiyomi.ui.manga.chapter
 
-import android.app.Activity
 import android.content.Context
 import android.util.AttributeSet
 import android.view.View
+import androidx.core.view.isVisible
+import com.bluelinelabs.conductor.Router
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.ui.manga.MangaPresenter
+import eu.kanade.tachiyomi.util.view.popupMenu
 import eu.kanade.tachiyomi.widget.ExtendedNavigationView
 import eu.kanade.tachiyomi.widget.TabbedBottomSheetDialog
 
 class ChaptersSettingsSheet(
-    activity: Activity,
+    private val router: Router,
     private val presenter: MangaPresenter,
     onGroupClickListener: (ExtendedNavigationView.Group) -> Unit
-) : TabbedBottomSheetDialog(activity) {
+) : TabbedBottomSheetDialog(router) {
 
     val filters: Filter
     private val sort: Sort
     private val display: Display
 
     init {
-        filters = Filter(activity)
+        filters = Filter(router.activity!!)
         filters.onGroupClicked = onGroupClickListener
 
-        sort = Sort(activity)
+        sort = Sort(router.activity!!)
         sort.onGroupClicked = onGroupClickListener
 
-        display = Display(activity)
+        display = Display(router.activity!!)
         display.onGroupClicked = onGroupClickListener
+
+        binding.menu.isVisible = true
+        binding.menu.setOnClickListener { it.post { showPopupMenu(it) } }
     }
 
     override fun getTabViews(): List<View> = listOf(
@@ -43,6 +48,23 @@ class ChaptersSettingsSheet(
         R.string.action_display
     )
 
+    private fun showPopupMenu(view: View) {
+        view.popupMenu(
+            R.menu.default_chapter_filter,
+            {
+            },
+            {
+                when (this.itemId) {
+                    R.id.set_as_default -> {
+                        SetChapterSettingsDialog(presenter.manga).showDialog(router)
+                        true
+                    }
+                    else -> true
+                }
+            }
+        )
+    }
+
     /**
      * Filters group (unread, downloaded, ...).
      */

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/MangaChaptersHeaderAdapter.kt

@@ -66,7 +66,7 @@ class MangaChaptersHeaderAdapter(
             } else {
                 view.context.getResourceColor(R.attr.colorOnPrimary)
             }
-            DrawableCompat.setTint(binding.btnChaptersFilter.icon, filterColor)
+            DrawableCompat.setTint(binding.btnChaptersFilter.drawable, filterColor)
 
             merge(view.clicks(), binding.btnChaptersFilter.clicks())
                 .onEach { controller.showSettingsSheet() }

+ 48 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/SetChapterSettingsDialog.kt

@@ -0,0 +1,48 @@
+package eu.kanade.tachiyomi.ui.manga.chapter
+
+import android.app.Dialog
+import android.os.Bundle
+import com.afollestad.materialdialogs.MaterialDialog
+import com.afollestad.materialdialogs.customview.customView
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.ui.base.controller.DialogController
+import eu.kanade.tachiyomi.util.chapter.ChapterSettingsHelper
+import eu.kanade.tachiyomi.util.system.toast
+import eu.kanade.tachiyomi.widget.DialogCheckboxView
+
+class SetChapterSettingsDialog(bundle: Bundle? = null) : DialogController(bundle) {
+
+    constructor(manga: Manga) : this(
+        Bundle().apply {
+            putSerializable(MANGA_KEY, manga)
+        }
+    )
+
+    override fun onCreateDialog(savedViewState: Bundle?): Dialog {
+        val view = DialogCheckboxView(activity!!).apply {
+            setDescription(R.string.confirm_set_chapter_settings)
+            setOptionDescription(R.string.also_set_chapter_settings_for_library)
+        }
+
+        return MaterialDialog(activity!!)
+            .title(R.string.chapter_settings)
+            .customView(
+                view = view,
+                horizontalPadding = true
+            )
+            .positiveButton(android.R.string.ok) {
+                ChapterSettingsHelper.setGlobalSettings(args.getSerializable(MANGA_KEY)!! as Manga)
+                if (view.isChecked()) {
+                    ChapterSettingsHelper.updateAllMangasWithGlobalDefaults()
+                }
+
+                activity?.toast(activity!!.getString(R.string.chapter_settings_updated))
+            }
+            .negativeButton(android.R.string.cancel)
+    }
+
+    private companion object {
+        const val MANGA_KEY = "manga"
+    }
+}

+ 50 - 25
app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt

@@ -105,13 +105,27 @@ class MangaInfoHeaderAdapter(
                     isVisible = true
 
                     if (trackCount > 0) {
-                        setIconResource(R.drawable.ic_done_24dp)
-                        text = view.context.resources.getQuantityString(R.plurals.num_trackers, trackCount, trackCount)
-                        isChecked = true
+                        setCompoundDrawablesWithIntrinsicBounds(
+                            null,
+                            ContextCompat.getDrawable(context, R.drawable.ic_done_24dp),
+                            null,
+                            null
+                        )
+                        text = view.context.resources.getQuantityString(
+                            R.plurals.num_trackers,
+                            trackCount,
+                            trackCount
+                        )
+                        isSelected = true
                     } else {
-                        setIconResource(R.drawable.ic_sync_24dp)
+                        setCompoundDrawablesWithIntrinsicBounds(
+                            null,
+                            ContextCompat.getDrawable(context, R.drawable.ic_sync_24dp),
+                            null,
+                            null
+                        )
                         text = view.context.getString(R.string.manga_tracking_tab)
-                        isChecked = false
+                        isSelected = false
                     }
 
                     clicks()
@@ -128,12 +142,6 @@ class MangaInfoHeaderAdapter(
                     .onEach { controller.openMangaInWebView() }
                     .launchIn(scope)
                 binding.btnWebview.setTooltip(R.string.action_open_in_web_view)
-
-                binding.btnShare.isVisible = true
-                binding.btnShare.clicks()
-                    .onEach { controller.shareManga() }
-                    .launchIn(scope)
-                binding.btnShare.setTooltip(R.string.action_share)
             }
 
             binding.mangaFullTitle.longClicks()
@@ -285,14 +293,24 @@ class MangaInfoHeaderAdapter(
 
                 // Update genres list
                 if (!manga.genre.isNullOrBlank()) {
-                    binding.mangaGenresTagsCompactChips.setChips(manga.getGenres(), controller::performSearch)
-                    binding.mangaGenresTagsFullChips.setChips(manga.getGenres(), controller::performSearch)
+                    binding.mangaGenresTagsCompactChips.setChips(
+                        manga.getGenres(),
+                        controller::performSearch
+                    )
+                    binding.mangaGenresTagsFullChips.setChips(
+                        manga.getGenres(),
+                        controller::performSearch
+                    )
                 } else {
                     binding.mangaGenresTagsWrapper.isVisible = false
                 }
 
                 // Handle showing more or less info
-                merge(binding.mangaSummarySection.clicks(), binding.mangaSummaryText.clicks(), binding.mangaInfoToggle.clicks())
+                merge(
+                    binding.mangaSummarySection.clicks(),
+                    binding.mangaSummaryText.clicks(),
+                    binding.mangaInfoToggle.clicks()
+                )
                     .onEach { toggleMangaInfo(view.context) }
                     .launchIn(scope)
 
@@ -310,20 +328,22 @@ class MangaInfoHeaderAdapter(
 
         private fun toggleMangaInfo(context: Context) {
             val isExpanded =
-                binding.mangaInfoToggle.text == context.getString(R.string.manga_info_collapse)
+                binding.mangaInfoToggle.contentDescription == context.getString(R.string.manga_info_collapse)
 
             with(binding.mangaInfoToggle) {
-                text = if (isExpanded) {
+                contentDescription = if (isExpanded) {
                     context.getString(R.string.manga_info_expand)
                 } else {
                     context.getString(R.string.manga_info_collapse)
                 }
 
-                icon = if (isExpanded) {
-                    context.getDrawable(R.drawable.ic_baseline_expand_more_24dp)
-                } else {
-                    context.getDrawable(R.drawable.ic_baseline_expand_less_24dp)
-                }
+                setImageDrawable(
+                    if (isExpanded) {
+                        context.getDrawable(R.drawable.ic_baseline_expand_more_24dp)
+                    } else {
+                        context.getDrawable(R.drawable.ic_baseline_expand_less_24dp)
+                    }
+                )
             }
 
             with(binding.mangaSummaryText) {
@@ -355,13 +375,18 @@ class MangaInfoHeaderAdapter(
             // Set the Favorite drawable to the correct one.
             // Border drawable if false, filled drawable if true.
             binding.btnFavorite.apply {
-                icon = ContextCompat.getDrawable(
-                    context,
-                    if (isFavorite) R.drawable.ic_favorite_24dp else R.drawable.ic_favorite_border_24dp
+                setCompoundDrawablesWithIntrinsicBounds(
+                    null,
+                    ContextCompat.getDrawable(
+                        context,
+                        if (isFavorite) R.drawable.ic_favorite_24dp else R.drawable.ic_favorite_border_24dp
+                    ),
+                    null,
+                    null
                 )
                 text =
                     context.getString(if (isFavorite) R.string.in_library else R.string.add_to_library)
-                isChecked = isFavorite
+                isSelected = isFavorite
             }
         }
     }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackReadingDatesDialog.kt

@@ -9,9 +9,9 @@ import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
-import java.util.Calendar
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.Calendar
 
 class SetTrackReadingDatesDialog<T> : DialogController
         where T : Controller, T : SetTrackReadingDatesDialog.Listener {

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackHolder.kt

@@ -5,8 +5,8 @@ import androidx.core.view.isVisible
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.databinding.TrackItemBinding
 import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder
-import java.text.DateFormat
 import uy.kohesive.injekt.injectLazy
+import java.text.DateFormat
 
 class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter) : BaseViewHolder(binding.root) {
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt

@@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.data.track.TrackService
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
-import java.util.concurrent.TimeUnit
 import kotlinx.android.synthetic.main.track_search_dialog.view.progress
 import kotlinx.android.synthetic.main.track_search_dialog.view.track_search
 import kotlinx.android.synthetic.main.track_search_dialog.view.track_search_list
@@ -26,6 +25,7 @@ import reactivecircus.flowbinding.android.widget.itemClicks
 import reactivecircus.flowbinding.android.widget.textChanges
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.concurrent.TimeUnit
 
 class TrackSearchDialog : DialogController {
 

+ 6 - 4
app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt

@@ -10,9 +10,9 @@ import com.afollestad.materialdialogs.MaterialDialog
 import com.mikepenz.aboutlibraries.LibsBuilder
 import eu.kanade.tachiyomi.BuildConfig
 import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.updater.UpdateChecker
 import eu.kanade.tachiyomi.data.updater.UpdateResult
 import eu.kanade.tachiyomi.data.updater.UpdaterService
+import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
 import eu.kanade.tachiyomi.ui.setting.SettingsController
 import eu.kanade.tachiyomi.util.lang.launchNow
@@ -23,19 +23,19 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory
 import eu.kanade.tachiyomi.util.preference.titleRes
 import eu.kanade.tachiyomi.util.system.copyToClipboard
 import eu.kanade.tachiyomi.util.system.toast
+import timber.log.Timber
 import java.text.DateFormat
 import java.text.ParseException
 import java.text.SimpleDateFormat
 import java.util.Locale
 import java.util.TimeZone
-import timber.log.Timber
 
 class AboutController : SettingsController() {
 
     /**
      * Checks for new releases
      */
-    private val updateChecker by lazy { UpdateChecker.getUpdateChecker() }
+    private val updateChecker by lazy { GithubUpdateChecker() }
 
     private val dateFormat: DateFormat = preferences.dateFormat()
 
@@ -234,7 +234,9 @@ class AboutController : SettingsController() {
             val buildTime = inputDf.parse(BuildConfig.BUILD_TIME)
 
             val outputDf = DateFormat.getDateTimeInstance(
-                DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault()
+                DateFormat.MEDIUM,
+                DateFormat.SHORT,
+                Locale.getDefault()
             )
             outputDf.timeZone = TimeZone.getDefault()
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt

@@ -7,7 +7,6 @@ import androidx.preference.PreferenceScreen
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.download.DownloadManager
 import eu.kanade.tachiyomi.data.download.DownloadService
-import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
 import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
 import eu.kanade.tachiyomi.ui.base.controller.RootController
 import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
@@ -28,6 +27,7 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
 import eu.kanade.tachiyomi.util.system.openInBrowser
 import rx.android.schedulers.AndroidSchedulers
 import uy.kohesive.injekt.injectLazy
+import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
 
 class MoreController :
     SettingsController(),

+ 33 - 21
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt

@@ -21,6 +21,7 @@ import android.view.animation.Animation
 import android.view.animation.AnimationUtils
 import android.widget.SeekBar
 import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.isVisible
 import androidx.core.view.setPadding
 import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
@@ -56,8 +57,6 @@ import eu.kanade.tachiyomi.util.view.showBar
 import eu.kanade.tachiyomi.util.view.snack
 import eu.kanade.tachiyomi.widget.SimpleAnimationListener
 import eu.kanade.tachiyomi.widget.SimpleSeekBarListener
-import java.io.File
-import kotlin.math.abs
 import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.drop
@@ -67,6 +66,8 @@ import kotlinx.coroutines.flow.sample
 import nucleus.factory.RequiresPresenter
 import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
+import java.io.File
+import kotlin.math.abs
 
 /**
  * Activity containing the reader of Tachiyomi. This activity is mostly a container of the
@@ -290,24 +291,27 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
 
         ViewCompat.setOnApplyWindowInsetsListener(binding.readerMenu) { _, insets ->
             if (!window.isDefaultBar()) {
+                val systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
                 binding.readerMenu.setPadding(
-                    insets.systemWindowInsetLeft,
-                    insets.systemWindowInsetTop,
-                    insets.systemWindowInsetRight,
-                    insets.systemWindowInsetBottom
+                    systemInsets.left,
+                    systemInsets.top,
+                    systemInsets.right,
+                    systemInsets.bottom
                 )
             }
             insets
         }
 
         // Init listeners on bottom menu
-        binding.pageSeekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
-            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
-                if (viewer != null && fromUser) {
-                    moveToPageIndex(value)
+        binding.pageSeekbar.setOnSeekBarChangeListener(
+            object : SimpleSeekBarListener() {
+                override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
+                    if (viewer != null && fromUser) {
+                        moveToPageIndex(value)
+                    }
                 }
             }
-        })
+        )
         binding.leftChapter.setOnClickListener {
             if (viewer != null) {
                 if (viewer is R2LPagerViewer) {
@@ -347,12 +351,14 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
 
             if (animate) {
                 val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_top)
-                toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() {
-                    override fun onAnimationStart(animation: Animation) {
-                        // Fix status bar being translucent the first time it's opened.
-                        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
+                toolbarAnimation.setAnimationListener(
+                    object : SimpleAnimationListener() {
+                        override fun onAnimationStart(animation: Animation) {
+// Fix status bar being translucent the first time it's opened.
+                            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
+                        }
                     }
-                })
+                )
                 binding.toolbar.startAnimation(toolbarAnimation)
 
                 val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.enter_from_bottom)
@@ -371,11 +377,13 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
 
             if (animate) {
                 val toolbarAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_top)
-                toolbarAnimation.setAnimationListener(object : SimpleAnimationListener() {
-                    override fun onAnimationEnd(animation: Animation) {
-                        binding.readerMenu.isVisible = false
+                toolbarAnimation.setAnimationListener(
+                    object : SimpleAnimationListener() {
+                        override fun onAnimationEnd(animation: Animation) {
+                            binding.readerMenu.isVisible = false
+                        }
                     }
-                })
+                )
                 binding.toolbar.startAnimation(toolbarAnimation)
 
                 val bottomAnimation = AnimationUtils.loadAnimation(this, R.anim.exit_to_bottom)
@@ -572,9 +580,13 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
      * Called from the presenter when a page is ready to be shared. It shows Android's default
      * sharing tool.
      */
-    fun onShareImageResult(file: File) {
+    fun onShareImageResult(file: File, page: ReaderPage) {
+        val manga = presenter.manga ?: return
+        val chapter = page.chapter.chapter
+
         val stream = file.getUriCompat(this)
         val intent = Intent(Intent.ACTION_SEND).apply {
+            putExtra(Intent.EXTRA_TEXT, getString(R.string.share_page_info, manga.title, chapter.name, page.number))
             putExtra(Intent.EXTRA_STREAM, stream)
             flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
             type = "image/*"

+ 39 - 29
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderColorFilterSheet.kt

@@ -74,45 +74,55 @@ class ReaderColorFilterSheet(private val activity: ReaderActivity) : BottomSheet
         }
         binding.colorFilterMode.setSelection(preferences.colorFilterMode().get(), false)
 
-        binding.seekbarColorFilterAlpha.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
-            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
-                if (fromUser) {
-                    setColorValue(value, ALPHA_MASK, 24)
+        binding.seekbarColorFilterAlpha.setOnSeekBarChangeListener(
+            object : SimpleSeekBarListener() {
+                override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
+                    if (fromUser) {
+                        setColorValue(value, ALPHA_MASK, 24)
+                    }
                 }
             }
-        })
-
-        binding.seekbarColorFilterRed.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
-            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
-                if (fromUser) {
-                    setColorValue(value, RED_MASK, 16)
+        )
+
+        binding.seekbarColorFilterRed.setOnSeekBarChangeListener(
+            object : SimpleSeekBarListener() {
+                override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
+                    if (fromUser) {
+                        setColorValue(value, RED_MASK, 16)
+                    }
                 }
             }
-        })
-
-        binding.seekbarColorFilterGreen.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
-            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
-                if (fromUser) {
-                    setColorValue(value, GREEN_MASK, 8)
+        )
+
+        binding.seekbarColorFilterGreen.setOnSeekBarChangeListener(
+            object : SimpleSeekBarListener() {
+                override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
+                    if (fromUser) {
+                        setColorValue(value, GREEN_MASK, 8)
+                    }
                 }
             }
-        })
-
-        binding.seekbarColorFilterBlue.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
-            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
-                if (fromUser) {
-                    setColorValue(value, BLUE_MASK, 0)
+        )
+
+        binding.seekbarColorFilterBlue.setOnSeekBarChangeListener(
+            object : SimpleSeekBarListener() {
+                override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
+                    if (fromUser) {
+                        setColorValue(value, BLUE_MASK, 0)
+                    }
                 }
             }
-        })
-
-        binding.brightnessSeekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
-            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
-                if (fromUser) {
-                    preferences.customBrightnessValue().set(value)
+        )
+
+        binding.brightnessSeekbar.setOnSeekBarChangeListener(
+            object : SimpleSeekBarListener() {
+                override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
+                    if (fromUser) {
+                        preferences.customBrightnessValue().set(value)
+                    }
                 }
             }
-        })
+        )
     }
 
     override fun onStart() {

+ 4 - 4
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt

@@ -27,9 +27,6 @@ import eu.kanade.tachiyomi.util.lang.takeBytes
 import eu.kanade.tachiyomi.util.storage.DiskUtil
 import eu.kanade.tachiyomi.util.system.ImageUtil
 import eu.kanade.tachiyomi.util.updateCoverLastModified
-import java.io.File
-import java.util.Date
-import java.util.concurrent.TimeUnit
 import rx.Completable
 import rx.Observable
 import rx.Subscription
@@ -38,6 +35,9 @@ import rx.schedulers.Schedulers
 import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.io.File
+import java.util.Date
+import java.util.concurrent.TimeUnit
 
 /**
  * Presenter used by the activity to perform background operations.
@@ -569,7 +569,7 @@ class ReaderPresenter(
             .subscribeOn(Schedulers.io())
             .observeOn(AndroidSchedulers.mainThread())
             .subscribeFirst(
-                { view, file -> view.onShareImageResult(file) },
+                { view, file -> view.onShareImageResult(file, page) },
                 { _, _ -> /* Empty */ }
             )
     }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt

@@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
 import eu.kanade.tachiyomi.util.system.ImageUtil
+import rx.Observable
 import java.io.File
 import java.io.FileInputStream
-import rx.Observable
 
 /**
  * Loader used to load a chapter from a directory given on [file].

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt

@@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.ui.reader.loader
 import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 import eu.kanade.tachiyomi.util.storage.EpubFile
-import java.io.File
 import rx.Observable
+import java.io.File
 
 /**
  * Loader used to load a chapter from a .epub file.

+ 3 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt

@@ -6,9 +6,6 @@ import eu.kanade.tachiyomi.source.online.HttpSource
 import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
 import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 import eu.kanade.tachiyomi.util.lang.plusAssign
-import java.util.concurrent.PriorityBlockingQueue
-import java.util.concurrent.atomic.AtomicInteger
-import kotlin.math.min
 import rx.Completable
 import rx.Observable
 import rx.schedulers.Schedulers
@@ -18,6 +15,9 @@ import rx.subscriptions.CompositeSubscription
 import timber.log.Timber
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
+import java.util.concurrent.PriorityBlockingQueue
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.math.min
 
 /**
  * Loader used to load chapters from an online source.

+ 3 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt

@@ -4,14 +4,14 @@ import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
 import eu.kanade.tachiyomi.util.system.ImageUtil
+import junrar.Archive
+import junrar.rarfile.FileHeader
+import rx.Observable
 import java.io.File
 import java.io.InputStream
 import java.io.PipedInputStream
 import java.io.PipedOutputStream
 import java.util.concurrent.Executors
-import junrar.Archive
-import junrar.rarfile.FileHeader
-import rx.Observable
 
 /**
  * Loader used to load a chapter from a .rar or .cbr file.

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt

@@ -5,11 +5,11 @@ import eu.kanade.tachiyomi.source.model.Page
 import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
 import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
 import eu.kanade.tachiyomi.util.system.ImageUtil
+import rx.Observable
 import java.io.File
 import java.nio.charset.StandardCharsets
 import java.util.zip.ZipEntry
 import java.util.zip.ZipFile
-import rx.Observable
 
 /**
  * Loader used to load a chapter from a .zip or .cbz file.

+ 15 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt

@@ -0,0 +1,15 @@
+package eu.kanade.tachiyomi.ui.reader.viewer
+
+import eu.kanade.tachiyomi.data.database.models.Chapter
+import kotlin.math.floor
+
+object MissingChapters {
+
+    fun hasMissingChapters(higher: Chapter, lower: Chapter): Boolean {
+        return hasMissingChapters(higher.chapter_number, lower.chapter_number)
+    }
+
+    fun hasMissingChapters(higherChapterNumber: Float, lowerChapterNumber: Float): Boolean {
+        return floor(higherChapterNumber) - floor(lowerChapterNumber) - 1f > 0f
+    }
+}

+ 6 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderProgressBar.kt

@@ -63,9 +63,12 @@ class ReaderProgressBar @JvmOverloads constructor(
      */
     private val rotationAnimation by lazy {
         RotateAnimation(
-            0f, 360f,
-            Animation.RELATIVE_TO_SELF, 0.5f,
-            Animation.RELATIVE_TO_SELF, 0.5f
+            0f,
+            360f,
+            Animation.RELATIVE_TO_SELF,
+            0.5f,
+            Animation.RELATIVE_TO_SELF,
+            0.5f
         ).apply {
             interpolator = LinearInterpolator()
             repeatCount = Animation.INFINITE

Some files were not shown because too many files changed in this diff