From 867a5a3ea05615977542d321e634346bb8813068 Mon Sep 17 00:00:00 2001 From: arkon Date: Sat, 23 Apr 2022 10:52:34 -0400 Subject: [PATCH 01/44] Move clear webview data action to network group (cherry picked from commit bf0bb5aa88f91b0567cf9de085a35d660e7e41a1) --- .../ui/setting/SettingsAdvancedController.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt index 95105981f..e94d6be08 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt @@ -143,12 +143,6 @@ class SettingsAdvancedController : SettingsController() { titleRes = R.string.pref_auto_clear_chapter_cache defaultValue = false } - preference { - key = "pref_clear_webview_data" - titleRes = R.string.pref_clear_webview_data - - onClick { clearWebViewData() } - } preference { key = "pref_clear_database" titleRes = R.string.pref_clear_database @@ -172,6 +166,12 @@ class SettingsAdvancedController : SettingsController() { activity?.toast(R.string.cookies_cleared) } } + preference { + key = "pref_clear_webview_data" + titleRes = R.string.pref_clear_webview_data + + onClick { clearWebViewData() } + } intListPreference { key = Keys.dohProvider titleRes = R.string.pref_dns_over_https From 0eb5a3176b27b9e852a6b56bd336d79776c8f9a5 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Apr 2022 09:42:26 -0400 Subject: [PATCH 02/44] Delete entire app_webview folder when clearing WebView data (cherry picked from commit 6e95fde4ece64e5959c04bb4b7fb69299ef37ca5) --- .../kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt index e94d6be08..594ae3894 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt @@ -48,6 +48,7 @@ import eu.kanade.tachiyomi.util.system.toast import logcat.LogPriority import rikka.sui.Sui import uy.kohesive.injekt.injectLazy +import java.io.File import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys class SettingsAdvancedController : SettingsController() { @@ -301,6 +302,7 @@ class SettingsAdvancedController : SettingsController() { webview.clearHistory() webview.clearSslPreferences() WebStorage.getInstance().deleteAllData() + activity?.applicationInfo?.dataDir?.let { File("$it/app_webview/").deleteRecursively() } activity?.toast(R.string.webview_data_deleted) } catch (e: Throwable) { logcat(LogPriority.ERROR, e) From 615b01a006149f221f2266e40977a039946f399f Mon Sep 17 00:00:00 2001 From: ItsLogic <38233332+ItsLogic@users.noreply.github.com> Date: Sun, 24 Apr 2022 20:21:21 +0100 Subject: [PATCH 03/44] Fix chapter transition setting for one page chapters (#6998) (cherry picked from commit 5e32b8e49fc7879559366357743bc450099453ca) --- .../tachiyomi/ui/reader/viewer/pager/PagerViewer.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt index 8322285d2..364740fad 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt @@ -66,9 +66,14 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { set(value) { field = value if (value) { - awaitingIdleViewerChapters?.let { - setChaptersInternal(it) + awaitingIdleViewerChapters?.let { viewerChapters -> + setChaptersInternal(viewerChapters) awaitingIdleViewerChapters = null + if (viewerChapters.currChapter.pages?.size == 1) { + adapter.nextTransition?.to?.let { + activity.requestPreloadChapter(it) + } + } } } } From 853f949140a6594bc526590edd470775bd9cad69 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Apr 2022 15:25:08 -0400 Subject: [PATCH 04/44] Add battery not low restriction for global updates (closes #6980) (cherry picked from commit 3feea7114614726cd0f5b87729a6c6195d180949) --- .../eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt | 2 ++ .../eu/kanade/tachiyomi/data/preference/PreferenceValues.kt | 1 + .../tachiyomi/ui/setting/SettingsLibraryController.kt | 6 ++++-- app/src/main/res/values/strings.xml | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt index 218c82554..be2c51ab2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt @@ -8,6 +8,7 @@ import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager import androidx.work.Worker import androidx.work.WorkerParameters +import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI import eu.kanade.tachiyomi.data.preference.PreferencesHelper @@ -43,6 +44,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresCharging(DEVICE_CHARGING in restrictions) + .setRequiresBatteryNotLow(DEVICE_BATTERY_NOT_LOW in restrictions) .build() val request = PeriodicWorkRequestBuilder( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt index 9ec000569..28cadea0c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt @@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.R const val DEVICE_ONLY_ON_WIFI = "wifi" const val DEVICE_CHARGING = "ac" +const val DEVICE_BATTERY_NOT_LOW = "battery_not_low" const val MANGA_NON_COMPLETED = "manga_ongoing" const val MANGA_HAS_UNREAD = "manga_fully_read" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt index 98b9f3fa0..70511823c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.library.LibraryUpdateJob +import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD @@ -159,8 +160,8 @@ class SettingsLibraryController : SettingsController() { multiSelectListPreference { bindTo(preferences.libraryUpdateDeviceRestriction()) titleRes = R.string.pref_library_update_restriction - entriesRes = arrayOf(R.string.connected_to_wifi, R.string.charging) - entryValues = arrayOf(DEVICE_ONLY_ON_WIFI, DEVICE_CHARGING) + entriesRes = arrayOf(R.string.connected_to_wifi, R.string.charging, R.string.battery_not_low) + entryValues = arrayOf(DEVICE_ONLY_ON_WIFI, DEVICE_CHARGING, DEVICE_BATTERY_NOT_LOW) visibleIf(preferences.libraryUpdateInterval()) { it > 0 } @@ -177,6 +178,7 @@ class SettingsLibraryController : SettingsController() { when (it) { DEVICE_ONLY_ON_WIFI -> context.getString(R.string.connected_to_wifi) DEVICE_CHARGING -> context.getString(R.string.charging) + DEVICE_BATTERY_NOT_LOW -> context.getString(R.string.battery_not_low) else -> it } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a3e513286..d3fba8367 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -223,6 +223,7 @@ Automatic updates device restrictions Only on Wi-Fi Charging + Battery not low Restrictions: %s Skip updating titles From c8ae936ce9c8e09d3ec3f6bc1dd036bd0a318b52 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Apr 2022 15:32:50 -0400 Subject: [PATCH 05/44] Default to downloading as CBZ (closes #6942) Generally seems fine. People with weak devices may experience some issues, but they can toggle it off/extract the archives separately if needed. (cherry picked from commit 883945e3e8b4c3fe5ec1bb151c247db7404b037c) --- .../eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 82135985c..05971e4ac 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -204,7 +204,7 @@ class PreferencesHelper(val context: Context) { fun downloadOnlyOverWifi() = prefs.getBoolean(Keys.downloadOnlyOverWifi, true) - fun saveChaptersAsCBZ() = flowPrefs.getBoolean("save_chapter_as_cbz", false) + fun saveChaptersAsCBZ() = flowPrefs.getBoolean("save_chapter_as_cbz", true) fun folderPerManga() = prefs.getBoolean(Keys.folderPerManga, false) From ba4346204132879ce8aaa37882af4adcf0e63657 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Apr 2022 15:35:05 -0400 Subject: [PATCH 06/44] Fix update warning notifications being cut off (fixes #6983) (cherry picked from commit 20145f7a12c5f14a27ab01f16ceee3cfca77fdab) --- .../java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt | 2 +- .../eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt index ad11fb52c..5d9f729f6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt @@ -190,7 +190,7 @@ internal class DownloadNotifier(private val context: Context) { fun onWarning(reason: String, timeout: Long? = null) { with(errorNotificationBuilder) { setContentTitle(context.getString(R.string.download_notifier_downloader_title)) - setContentText(reason) + setStyle(NotificationCompat.BigTextStyle().bigText(reason)) setSmallIcon(R.drawable.ic_warning_white_24dp) setAutoCancel(true) clearActions() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index 7ea91a629..5bcef312e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -93,7 +93,7 @@ class LibraryUpdateNotifier(private val context: Context) { fun showQueueSizeWarningNotification() { val notificationBuilder = context.notificationBuilder(Notifications.CHANNEL_LIBRARY_PROGRESS) { setContentTitle(context.getString(R.string.label_warning)) - setContentText(context.getString(R.string.notification_size_warning)) + setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(R.string.notification_size_warning))) setSmallIcon(R.drawable.ic_warning_white_24dp) setTimeoutAfter(Downloader.WARNING_NOTIF_TIMEOUT_MS) } From 8e34a30dcebe558a8803346728cfb87f6806ece1 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 24 Apr 2022 15:49:24 -0400 Subject: [PATCH 07/44] Fix skipped library entries and size warning notifications using same ID (cherry picked from commit 91ed3a4a5facaaa335d4408e7c5342dcca430f47) --- .../java/eu/kanade/tachiyomi/data/notification/Notifications.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt index f8a74abcd..1a5ed595e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt @@ -30,7 +30,7 @@ object Notifications { const val CHANNEL_LIBRARY_ERROR = "library_errors_channel" const val ID_LIBRARY_ERROR = -102 const val CHANNEL_LIBRARY_SKIPPED = "library_skipped_channel" - const val ID_LIBRARY_SKIPPED = -103 + const val ID_LIBRARY_SKIPPED = -104 /** * Notification channel and ids used by the downloader. From a409fde519334f00c78b0b558f92bae23237a33d Mon Sep 17 00:00:00 2001 From: FourTOne5 <59261191+FourTOne5@users.noreply.github.com> Date: Sun, 24 Apr 2022 13:36:14 -0700 Subject: [PATCH 08/44] Download new chapters when only excluded categories is selected (#6984) (cherry picked from commit 06bec0ad54954e0a0c76949206814747df5a8370) --- .../data/preference/PreferencesHelper.kt | 6 ++--- .../ui/setting/SettingsDownloadController.kt | 22 +++++++++---------- .../kanade/tachiyomi/util/MangaExtensions.kt | 19 +++++++++------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 05971e4ac..9fbe9fe03 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -278,10 +278,10 @@ class PreferencesHelper(val context: Context) { fun pinnedSources() = flowPrefs.getStringSet("pinned_catalogues", emptySet()) - fun downloadNew() = flowPrefs.getBoolean("download_new", false) + fun downloadNewChapter() = flowPrefs.getBoolean("download_new", false) - fun downloadNewCategories() = flowPrefs.getStringSet("download_new_categories", emptySet()) - fun downloadNewCategoriesExclude() = flowPrefs.getStringSet("download_new_categories_exclude", emptySet()) + fun downloadNewChapterCategories() = flowPrefs.getStringSet("download_new_categories", emptySet()) + fun downloadNewChapterCategoriesExclude() = flowPrefs.getStringSet("download_new_categories_exclude", emptySet()) fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt index 82c7cc839..74fce340b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt @@ -125,20 +125,20 @@ class SettingsDownloadController : SettingsController() { titleRes = R.string.pref_category_auto_download switchPreference { - bindTo(preferences.downloadNew()) + bindTo(preferences.downloadNewChapter()) titleRes = R.string.pref_download_new } preference { - bindTo(preferences.downloadNewCategories()) + bindTo(preferences.downloadNewChapterCategories()) titleRes = R.string.categories onClick { DownloadCategoriesDialog().showDialog(router) } - visibleIf(preferences.downloadNew()) { it } + visibleIf(preferences.downloadNewChapter()) { it } fun updateSummary() { - val selectedCategories = preferences.downloadNewCategories().get() + val selectedCategories = preferences.downloadNewChapterCategories().get() .mapNotNull { id -> categories.find { it.id == id.toInt() } } .sortedBy { it.order } val includedItemsText = if (selectedCategories.isEmpty()) { @@ -147,7 +147,7 @@ class SettingsDownloadController : SettingsController() { selectedCategories.joinToString { it.name } } - val excludedCategories = preferences.downloadNewCategoriesExclude().get() + val excludedCategories = preferences.downloadNewChapterCategoriesExclude().get() .mapNotNull { id -> categories.find { it.id == id.toInt() } } .sortedBy { it.order } val excludedItemsText = if (excludedCategories.isEmpty()) { @@ -163,10 +163,10 @@ class SettingsDownloadController : SettingsController() { } } - preferences.downloadNewCategories().asFlow() + preferences.downloadNewChapterCategories().asFlow() .onEach { updateSummary() } .launchIn(viewScope) - preferences.downloadNewCategoriesExclude().asFlow() + preferences.downloadNewChapterCategoriesExclude().asFlow() .onEach { updateSummary() } .launchIn(viewScope) } @@ -254,8 +254,8 @@ class SettingsDownloadController : SettingsController() { var selected = categories .map { when (it.id.toString()) { - in preferences.downloadNewCategories().get() -> QuadStateTextView.State.CHECKED.ordinal - in preferences.downloadNewCategoriesExclude().get() -> QuadStateTextView.State.INVERSED.ordinal + in preferences.downloadNewChapterCategories().get() -> QuadStateTextView.State.CHECKED.ordinal + in preferences.downloadNewChapterCategoriesExclude().get() -> QuadStateTextView.State.INVERSED.ordinal else -> QuadStateTextView.State.UNCHECKED.ordinal } } @@ -282,8 +282,8 @@ class SettingsDownloadController : SettingsController() { .map { categories[it].id.toString() } .toSet() - preferences.downloadNewCategories().set(included) - preferences.downloadNewCategoriesExclude().set(excluded) + preferences.downloadNewChapterCategories().set(included) + preferences.downloadNewChapterCategoriesExclude().set(excluded) } .setNegativeButton(android.R.string.cancel, null) .create() diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt index f1a4fb623..adc3b2c7d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt @@ -56,14 +56,14 @@ fun Manga.shouldDownloadNewChapters(db: DatabaseHelper, prefs: PreferencesHelper if (!favorite) return false // Boolean to determine if user wants to automatically download new chapters. - val downloadNew = prefs.downloadNew().get() - if (!downloadNew) return false + val downloadNewChapter = prefs.downloadNewChapter().get() + if (!downloadNewChapter) return false - val categoriesToDownload = prefs.downloadNewCategories().get().map(String::toInt) - val categoriesToExclude = prefs.downloadNewCategoriesExclude().get().map(String::toInt) + val includedCategories = prefs.downloadNewChapterCategories().get().map { it.toInt() } + val excludedCategories = prefs.downloadNewChapterCategoriesExclude().get().map { it.toInt() } - // Default: download from all categories - if (categoriesToDownload.isEmpty() && categoriesToExclude.isEmpty()) return true + // Default: Download from all categories + if (includedCategories.isEmpty() && excludedCategories.isEmpty()) return true // Get all categories, else default category (0) val categoriesForManga = @@ -72,8 +72,11 @@ fun Manga.shouldDownloadNewChapters(db: DatabaseHelper, prefs: PreferencesHelper .takeUnless { it.isEmpty() } ?: listOf(0) // In excluded category - if (categoriesForManga.intersect(categoriesToExclude).isNotEmpty()) return false + if (categoriesForManga.any { it in excludedCategories }) return false + + // Included category not selected + if (includedCategories.isEmpty()) return true // In included category - return categoriesForManga.intersect(categoriesToDownload).isNotEmpty() + return categoriesForManga.any { it in includedCategories } } From 0721de5b81a0c63829becaa4a5fdb77724a3334b Mon Sep 17 00:00:00 2001 From: arkon Date: Wed, 27 Apr 2022 22:45:31 -0400 Subject: [PATCH 09/44] Add links to website FAQ for library update and download warning notifications (cherry picked from commit 70698e64940eb01032e948245a01c4191ccd60f0) --- .../eu/kanade/tachiyomi/data/download/DownloadNotifier.kt | 4 +++- .../java/eu/kanade/tachiyomi/data/download/Downloader.kt | 3 +++ .../kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt | 5 +++++ app/src/main/res/values/strings.xml | 4 ++-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt index 5d9f729f6..9c5f37699 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.data.download +import android.app.PendingIntent import android.content.Context import android.graphics.BitmapFactory import androidx.core.app.NotificationCompat @@ -187,7 +188,7 @@ internal class DownloadNotifier(private val context: Context) { * @param timeout duration after which to automatically dismiss the notification. * Only works on Android 8+. */ - fun onWarning(reason: String, timeout: Long? = null) { + fun onWarning(reason: String, timeout: Long? = null, contentIntent: PendingIntent? = null) { with(errorNotificationBuilder) { setContentTitle(context.getString(R.string.download_notifier_downloader_title)) setStyle(NotificationCompat.BigTextStyle().bigText(reason)) @@ -197,6 +198,7 @@ internal class DownloadNotifier(private val context: Context) { setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) setProgress(0, 0, false) timeout?.let { setTimeoutAfter(it) } + contentIntent?.let { setContentIntent(it) } show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index e60046be5..e2f84f646 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -11,6 +11,8 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.DownloadQueue +import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier +import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.UnmeteredSource @@ -285,6 +287,7 @@ class Downloader( notifier.onWarning( context.getString(R.string.download_queue_size_warning), WARNING_NOTIF_TIMEOUT_MS, + NotificationHandler.openUrl(context, LibraryUpdateNotifier.HELP_WARNING_URL), ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index 5bcef312e..b85533267 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -96,6 +96,7 @@ class LibraryUpdateNotifier(private val context: Context) { setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(R.string.notification_size_warning))) setSmallIcon(R.drawable.ic_warning_white_24dp) setTimeoutAfter(Downloader.WARNING_NOTIF_TIMEOUT_MS) + setContentIntent(NotificationHandler.openUrl(context, HELP_WARNING_URL)) } context.notificationManager.notify( @@ -340,6 +341,10 @@ class LibraryUpdateNotifier(private val context: Context) { } return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } + + companion object { + const val HELP_WARNING_URL = "https://tachiyomi.org/help/faq/#why-does-the-app-warn-about-large-bulk-updates-and-downloads" + } } private const val NOTIF_MAX_CHAPTERS = 5 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d3fba8367..2105a2a12 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -721,12 +721,12 @@ Couldn\'t download chapters. You can try again in the downloads section Couldn\'t download chapters due to low storage space - Warning: large bulk downloads may lead to sources becoming slower and/or blocking Tachiyomi + Warning: large bulk downloads may lead to sources becoming slower and/or blocking Tachiyomi. Tap to learn more. Checking for new chapters Updating library… (%1$d/%2$d) - Large updates harm sources and may lead to slower updates and also increased battery usage + Large updates harm sources and may lead to slower updates and also increased battery usage. Tap to learn more. New chapters found For %d title From bfa918140fa96b5305aec246c21b962a0e4d4b82 Mon Sep 17 00:00:00 2001 From: arkon Date: Thu, 28 Apr 2022 18:09:05 -0400 Subject: [PATCH 10/44] Fix Android 13 icon sizing (cherry picked from commit 9fdc803c14872e69e370483c356fa4edf5c8176c) --- .../drawable/ic_tachi_monochrome_launcher.xml | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/drawable/ic_tachi_monochrome_launcher.xml b/app/src/main/res/drawable/ic_tachi_monochrome_launcher.xml index 5995f044e..276da9df2 100644 --- a/app/src/main/res/drawable/ic_tachi_monochrome_launcher.xml +++ b/app/src/main/res/drawable/ic_tachi_monochrome_launcher.xml @@ -1,8 +1,12 @@ - - - - + + + + From a16b5d241b196995c2ff1909e6d18911d95ad77a Mon Sep 17 00:00:00 2001 From: FourTOne5 <59261191+FourTOne5@users.noreply.github.com> Date: Sat, 7 May 2022 08:00:24 +0600 Subject: [PATCH 11/44] Add `-r` flag to ShizukuInstaller `createCommand` (#7080) (cherry picked from commit 49d3ddb830eaf792ae393d6f10dc76a26ebd28ec) --- .../kanade/tachiyomi/extension/installer/ShizukuInstaller.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt index 1fa4b230b..2c3f617e0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt @@ -52,9 +52,9 @@ class ShizukuInstaller(private val service: Service) : Installer(service) { val size = service.getUriSize(entry.uri) ?: throw IllegalStateException() service.contentResolver.openInputStream(entry.uri)!!.use { val createCommand = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - "pm install-create --user current -i ${service.packageName} -S $size" + "pm install-create --user current -r -i ${service.packageName} -S $size" } else { - "pm install-create -i ${service.packageName} -S $size" + "pm install-create -r -i ${service.packageName} -S $size" } val createResult = exec(createCommand) sessionId = SESSION_ID_REGEX.find(createResult.out)?.value From 972cd98d7b99a3b0a32c5ce279cc26b191026c38 Mon Sep 17 00:00:00 2001 From: FourTOne5 <59261191+FourTOne5@users.noreply.github.com> Date: Sat, 7 May 2022 08:15:44 +0600 Subject: [PATCH 12/44] Fix removing manga from library reverts during global update (#7063) * Fix removing manga from library reverts during global update * Review Changes * Review changes 2 (cherry picked from commit c4088bad125b900df2e76207ffd9beaa1d08767a) --- .../data/library/LibraryUpdateService.kt | 74 +++++++++++-------- .../util/chapter/ChapterSourceSync.kt | 2 +- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index 08861b935..27f00d3ca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -174,6 +174,8 @@ class LibraryUpdateService( */ override fun onDestroy() { updateJob?.cancel() + // Despite what Android Studio + // states this can be null ioScope?.cancel() if (wakeLock.isHeld) { wakeLock.release() @@ -233,8 +235,7 @@ class LibraryUpdateService( /** * Adds list of manga to be updated. * - * @param category the ID of the category to update, or -1 if no category specified. - * @param target the target to update. + * @param categoryId the ID of the category to update, or -1 if no category specified. */ fun addMangaToQueue(categoryId: Int) { val libraryManga = db.getLibraryMangas().executeAsBlocking() @@ -274,12 +275,11 @@ class LibraryUpdateService( } /** - * Method that updates the given list of manga. It's called in a background thread, so it's safe + * Method that updates manga in [mangaToUpdate]. It's called in a background thread, so it's safe * to do heavy operations or network calls here. * For each manga it calls [updateManga] and updates the notification showing the current * progress. * - * @param mangaToUpdate the list to update * @return an observable delivering the progress of each update. */ suspend fun updateChapterList() { @@ -305,35 +305,38 @@ class LibraryUpdateService( return@async } + // Don't continue to update if manga not in library + db.getManga(manga.id!!).executeAsBlocking() ?: return@forEach + withUpdateNotification( currentlyUpdatingManga, progressCount, manga, - ) { manga -> + ) { mangaWithNotif -> try { when { - MANGA_NON_COMPLETED in restrictions && manga.status == SManga.COMPLETED -> { - skippedUpdates.add(manga to getString(R.string.skipped_reason_completed)) - } - MANGA_HAS_UNREAD in restrictions && manga.unreadCount != 0 -> { - skippedUpdates.add(manga to getString(R.string.skipped_reason_not_caught_up)) - } - MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasStarted -> { - skippedUpdates.add(manga to getString(R.string.skipped_reason_not_started)) - } + MANGA_NON_COMPLETED in restrictions && mangaWithNotif.status == SManga.COMPLETED -> + skippedUpdates.add(mangaWithNotif to getString(R.string.skipped_reason_completed)) + + MANGA_HAS_UNREAD in restrictions && mangaWithNotif.unreadCount != 0 -> + skippedUpdates.add(mangaWithNotif to getString(R.string.skipped_reason_not_caught_up)) + + MANGA_NON_READ in restrictions && mangaWithNotif.totalChapters > 0 && !mangaWithNotif.hasStarted -> + skippedUpdates.add(mangaWithNotif to getString(R.string.skipped_reason_not_started)) + else -> { // Convert to the manga that contains new chapters - val (newChapters, _) = updateManga(manga) + val (newChapters, _) = updateManga(mangaWithNotif) if (newChapters.isNotEmpty()) { - if (manga.shouldDownloadNewChapters(db, preferences)) { - downloadChapters(manga, newChapters) + if (mangaWithNotif.shouldDownloadNewChapters(db, preferences)) { + downloadChapters(mangaWithNotif, newChapters) hasDownloads.set(true) } // Convert to the manga that contains new chapters newUpdates.add( - manga to newChapters.sortedByDescending { ch -> ch.source_order } + mangaWithNotif to newChapters.sortedByDescending { ch -> ch.source_order } .toTypedArray(), ) } @@ -352,11 +355,11 @@ class LibraryUpdateService( e.message } } - failedUpdates.add(manga to errorMessage) + failedUpdates.add(mangaWithNotif to errorMessage) } if (preferences.autoUpdateTrackers()) { - updateTrackings(manga, loggedServices) + updateTrackings(mangaWithNotif, loggedServices) } } } @@ -404,6 +407,7 @@ class LibraryUpdateService( suspend fun updateManga(manga: Manga): Pair, List> { val source = sourceManager.getOrStub(manga.source) + var networkSManga: SManga? = null // Update manga details metadata if (preferences.autoUpdateMetadata()) { val updatedManga = source.getMangaDetails(manga.toMangaInfo()) @@ -415,14 +419,26 @@ class LibraryUpdateService( sManga.thumbnail_url = manga.thumbnail_url } - manga.copyFrom(sManga) - db.insertManga(manga).executeAsBlocking() + networkSManga = sManga } val chapters = source.getChapterList(manga.toMangaInfo()) .map { it.toSChapter() } - return syncChaptersWithSource(db, chapters, manga, source) + // Get manga from database to account for if it was removed + // from library or database + val dbManga = db.getManga(manga.id!!).executeAsBlocking() + ?: return Pair(emptyList(), emptyList()) + + // Copy into [dbManga] to retain favourite value + networkSManga?.let { + dbManga.copyFrom(it) + db.insertManga(dbManga).executeAsBlocking() + } + + // [dbmanga] was used so that manga data doesn't get overwritten + // incase manga gets new chapter + return syncChaptersWithSource(db, chapters, dbManga, source) } private suspend fun updateCovers() { @@ -445,16 +461,16 @@ class LibraryUpdateService( currentlyUpdatingManga, progressCount, manga, - ) { manga -> - sourceManager.get(manga.source)?.let { source -> + ) { mangaWithNotif -> + sourceManager.get(mangaWithNotif.source)?.let { source -> try { val networkManga = - source.getMangaDetails(manga.toMangaInfo()) + source.getMangaDetails(mangaWithNotif.toMangaInfo()) val sManga = networkManga.toSManga() - manga.prepUpdateCover(coverCache, sManga, true) + mangaWithNotif.prepUpdateCover(coverCache, sManga, true) sManga.thumbnail_url?.let { - manga.thumbnail_url = it - db.insertManga(manga).executeAsBlocking() + mangaWithNotif.thumbnail_url = it + db.insertManga(mangaWithNotif).executeAsBlocking() } } catch (e: Throwable) { // Ignore errors and continue diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt index 4aee9e246..162b919e4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt @@ -25,7 +25,7 @@ fun syncChaptersWithSource( db: DatabaseHelper, rawSourceChapters: List, manga: Manga, - source: Source, + source: Source ): Pair, List> { if (rawSourceChapters.isEmpty()) { throw NoChaptersException() From 196a8e68294e9d8a2bc5f804310f4ac917948389 Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 9 May 2022 08:45:26 -0400 Subject: [PATCH 13/44] Rename "navigation layout" to "tap zones" (cherry picked from commit c49d862fc58fb4a750ee6de006054823f94e3263) --- app/src/main/res/values/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2105a2a12..47cea213b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -284,8 +284,8 @@ Fullscreen - Show navigation layout overlay - Show tap zones when reader is opened + Show tap zones overlay + Briefly show when reader is opened Dual page split Invert dual page split placement If the placement of the dual page split doesn\'t match reading direction @@ -296,7 +296,7 @@ Show reading mode Briefly show current mode when reader is opened 32-bit color - Reduces banding, but impacts performance + Reduces banding, but may impact performance Crop borders On Off @@ -316,7 +316,7 @@ Navigation Volume keys Invert volume keys - Invert tapping + Invert tap zones None Horizontal Vertical @@ -346,7 +346,7 @@ Webtoon Continuous vertical Paged - Navigation layout + Tap zones Scale type Fit screen Stretch From aab7795b4cd99a1f07fa2f8c11eb3f12e3206779 Mon Sep 17 00:00:00 2001 From: nicki <72807749+curche@users.noreply.github.com> Date: Mon, 9 May 2022 20:33:40 +0530 Subject: [PATCH 14/44] Don't save categories in backup if not selected (#7101) Currently, manually created backups contain list of categories even if Categories option is not selected during Backup Prompt. This leads to empty categories being created when restoring such backup files This commit adds a check before saving categories list info to the backup file. The check is the same check which is used while backing up category info of manga in library Tested and worked successfully on app installed on Android 12 (cherry picked from commit 11c01235ac32c8fd3de864c37cab82367b4a9e41) --- .../data/backup/full/FullBackupManager.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt index 933cc7b66..193fab0b3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt @@ -55,7 +55,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { backup = Backup( backupManga(databaseManga, flags), - backupCategories(), + backupCategories(flags), emptyList(), backupExtensionInfo(databaseManga), ) @@ -133,10 +133,15 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { * * @return list of [BackupCategory] to be backed up */ - private fun backupCategories(): List { - return databaseHelper.getCategories() - .executeAsBlocking() - .map { BackupCategory.copyFrom(it) } + private fun backupCategories(options: Int): List { + // Check if user wants category information in backup + return if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) { + databaseHelper.getCategories() + .executeAsBlocking() + .map { BackupCategory.copyFrom(it) } + } else { + emptyList() + } } /** From 55b0b5769902957150e451059e85c364169e12ac Mon Sep 17 00:00:00 2001 From: CVIUS <84634607+CVIUS@users.noreply.github.com> Date: Tue, 10 May 2022 21:02:46 +0800 Subject: [PATCH 15/44] Use theme primary color for slider track (#7102) (cherry picked from commit bc053580ad21669cf507dea1aa14a8dd5cb59ef6) --- app/src/main/res/color/slider_active_track.xml | 5 +++++ app/src/main/res/color/slider_inactive_track.xml | 5 +++++ app/src/main/res/values/styles.xml | 2 ++ 3 files changed, 12 insertions(+) create mode 100644 app/src/main/res/color/slider_active_track.xml create mode 100644 app/src/main/res/color/slider_inactive_track.xml diff --git a/app/src/main/res/color/slider_active_track.xml b/app/src/main/res/color/slider_active_track.xml new file mode 100644 index 000000000..764d21bf3 --- /dev/null +++ b/app/src/main/res/color/slider_active_track.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/slider_inactive_track.xml b/app/src/main/res/color/slider_inactive_track.xml new file mode 100644 index 000000000..0f624c117 --- /dev/null +++ b/app/src/main/res/color/slider_inactive_track.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index dbf73d1a1..7130d28c4 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -192,6 +192,8 @@ From a4515ad2511c6dfb073582efefdf3ec7e2bb1915 Mon Sep 17 00:00:00 2001 From: nicki <72807749+curche@users.noreply.github.com> Date: Wed, 11 May 2022 02:34:40 +0530 Subject: [PATCH 16/44] Check for app updates by comparing semver (#7100) Instead of just checking whether the current app version *matches* with latest app version in GitHub Releases, compare the semver from the tag names to check whether the latter is greater and the app needs an update Reference: semver spec #11 https://semver.org/#spec-item-11 Co-authored-by: Andreas <6576096+ghostbear@users.noreply.github.com> Co-authored-by: Andreas <6576096+ghostbear@users.noreply.github.com> (cherry picked from commit e7ed130f2a4fcd7452737476189687fbd130c80d) --- .../kanade/tachiyomi/data/updater/AppUpdateChecker.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt index 9b1b317c9..60460dc98 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt @@ -56,6 +56,7 @@ class AppUpdateChecker { private fun isNewVersion(versionTag: String): Boolean { // Removes prefixes like "r" or "v" val newVersion = versionTag.replace("[^\\d.]".toRegex(), "") + val oldVersion = BuildConfig.VERSION_NAME.replace("[^\\d.]".toRegex(), "") return if (BuildConfig.PREVIEW) { // Preview builds: based on releases in "tachiyomiorg/tachiyomi-preview" repo @@ -64,7 +65,15 @@ class AppUpdateChecker { } else { // Release builds: based on releases in "tachiyomiorg/tachiyomi" repo // tagged as something like "v0.1.2" - newVersion != BuildConfig.VERSION_NAME + val newSemVer = newVersion.split(".").map { it.toInt() } + val oldSemVer = oldVersion.split(".").map { it.toInt() } + + oldSemVer.mapIndexed { index, i -> + if (newSemVer[index] > i) { + return true + } + } + false } } } From 8874193927555e8269174b067027e6c03066c66c Mon Sep 17 00:00:00 2001 From: arkon Date: Tue, 10 May 2022 17:53:19 -0400 Subject: [PATCH 17/44] Update build workflow actions (cherry picked from commit 8bee5accb7f8e22c1589ad78cec8fdb7dca68737) --- .github/workflows/build_pull_request.yml | 3 ++- .github/workflows/build_push.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index 06ca19e33..50c6fc2cf 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -24,9 +24,10 @@ jobs: uses: actions/dependency-review-action@v1 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: java-version: 11 + distribution: adopt - name: Copy CI gradle.properties run: | diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index ed28a7c0a..ee2efbd96 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -25,9 +25,10 @@ jobs: uses: gradle/wrapper-validation-action@v1 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: java-version: 11 + distribution: adopt - name: Copy CI gradle.properties run: | From b46fb7d1e19ca9af68a01566fa51e6f7a9420072 Mon Sep 17 00:00:00 2001 From: CVIUS <84634607+CVIUS@users.noreply.github.com> Date: Thu, 12 May 2022 10:35:30 +0800 Subject: [PATCH 18/44] Fix "Move to top" showing at the most top item in download queue (#7109) (cherry picked from commit b26daf8824e09922f063db6d6410b78d6281957b) --- .../main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt index 8f4623143..514357d65 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadHolder.kt @@ -89,7 +89,7 @@ class DownloadHolder(private val view: View, val adapter: DownloadAdapter) : view.popupMenu( menuRes = R.menu.download_single, initMenu = { - findItem(R.id.move_to_top).isVisible = bindingAdapterPosition != 0 + findItem(R.id.move_to_top).isVisible = bindingAdapterPosition > 1 findItem(R.id.move_to_bottom).isVisible = bindingAdapterPosition != adapter.itemCount - 1 }, From 6cb255e60aa09644109eaa38065108f49dc05d3a Mon Sep 17 00:00:00 2001 From: nzoba <55888232+nzoba@users.noreply.github.com> Date: Sat, 14 May 2022 03:42:23 +0200 Subject: [PATCH 19/44] Add switch to DownloadPageLoader when chapter is downloaded (#7119) (cherry picked from commit 63627c81ebd6b3a9b1ee017f385a72b6fc8c8a49) --- .../eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index fb954bab7..946df975e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.reader.loader.ChapterLoader +import eu.kanade.tachiyomi.ui.reader.loader.HttpPageLoader import eu.kanade.tachiyomi.ui.reader.model.InsertPage import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage @@ -345,6 +346,14 @@ class ReaderPresenter( * that the user doesn't have to wait too long to continue reading. */ private fun preload(chapter: ReaderChapter) { + if (chapter.pageLoader is HttpPageLoader) { + val manga = manga ?: return + val isDownloaded = downloadManager.isChapterDownloaded(chapter.chapter, manga) + if (isDownloaded) { + chapter.state = ReaderChapter.State.Wait + } + } + if (chapter.state != ReaderChapter.State.Wait && chapter.state !is ReaderChapter.State.Error) { return } From cecf532ffdd37c4db0b73d67ba5edbfec6fec1e8 Mon Sep 17 00:00:00 2001 From: CVIUS <84634607+CVIUS@users.noreply.github.com> Date: Sat, 14 May 2022 20:09:15 +0800 Subject: [PATCH 20/44] Fix category tabs incorrect scroll position (#7120) (cherry picked from commit 6d655ff7574c0c51c41cfc6b3abed57fb2115acc) --- .../eu/kanade/tachiyomi/ui/library/LibraryController.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 291892bc1..aa8b9efa1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -8,7 +8,6 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.appcompat.view.ActionMode -import androidx.core.view.doOnAttach import androidx.core.view.isVisible import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType @@ -304,8 +303,10 @@ class LibraryController( onTabsSettingsChanged(firstLaunch = true) // Delay the scroll position to allow the view to be properly measured. - view.doOnAttach { - (activity as? MainActivity)?.binding?.tabs?.setScrollPosition(binding.libraryPager.currentItem, 0f, true) + view.post { + if (isAttached) { + (activity as? MainActivity)?.binding?.tabs?.setScrollPosition(binding.libraryPager.currentItem, 0f, true) + } } // Send the manga map to child fragments after the adapter is updated. From fe803567560a5b7ea6858a456ad4957abc27e79c Mon Sep 17 00:00:00 2001 From: CVIUS <84634607+CVIUS@users.noreply.github.com> Date: Sat, 14 May 2022 20:51:04 +0800 Subject: [PATCH 21/44] Save reader progress when activity is paused (#7121) (cherry picked from commit f1ab34e27cbd8f26f87e34238af0863d4650b960) --- .../java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt | 5 +++++ .../java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 51fa73cc1..5b8a07af3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -226,6 +226,11 @@ class ReaderActivity : BaseRxActivity() { super.onSaveInstanceState(outState) } + override fun onPause() { + presenter.saveProgress() + super.onPause() + } + /** * Set menu visibility again on activity resume to apply immersive mode again if needed. * Helps with rotations. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index 946df975e..7a66b2fcb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -465,6 +465,10 @@ class ReaderPresenter( } } + fun saveProgress() { + getCurrentChapter()?.let { onChapterChanged(it) } + } + /** * Called from the activity to preload the given [chapter]. */ From 980709cccbd4afd05ade4178bcf3eca5dd53c040 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 15 May 2022 16:51:52 -0400 Subject: [PATCH 22/44] Use jsDelivr as fallback when GitHub can't be reached for extensions (closes #5517) Re-implementation of 24bb2f02dce135e0ceb2856618ecfc0e30dce875 (cherry picked from commit d61bfd7cafa09ff6c5f159c945984f2e8d9904b9) --- .../extension/api/ExtensionGithubApi.kt | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt index 115ff57a4..6869bbae9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt @@ -11,7 +11,9 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.util.lang.withIOContext +import eu.kanade.tachiyomi.util.system.logcat import kotlinx.serialization.Serializable +import logcat.LogPriority import uy.kohesive.injekt.injectLazy import java.util.Date import java.util.concurrent.TimeUnit @@ -21,11 +23,24 @@ internal class ExtensionGithubApi { private val networkService: NetworkHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy() + private var requiresFallbackSource = false + suspend fun findExtensions(): List { return withIOContext { - val extensions = networkService.client - .newCall(GET("${REPO_URL_PREFIX}index.min.json")) - .await() + val response = try { + networkService.client + .newCall(GET("${REPO_URL_PREFIX}index.min.json")) + .await() + } catch (e: Throwable) { + logcat(LogPriority.ERROR, e) { "Failed to get extensions from GitHub" } + requiresFallbackSource = true + + networkService.client + .newCall(GET("${FALLBACK_REPO_URL_PREFIX}index.min.json")) + .await() + } + + val extensions = response .parseAs>() .toExtensions() @@ -85,7 +100,7 @@ internal class ExtensionGithubApi { hasChangelog = it.hasChangelog == 1, sources = it.sources?.toExtensionSources() ?: emptyList(), apkName = it.apk, - iconUrl = "${REPO_URL_PREFIX}icon/${it.apk.replace(".apk", ".png")}", + iconUrl = "${getUrlPrefix()}icon/${it.apk.replace(".apk", ".png")}", ) } } @@ -101,11 +116,20 @@ internal class ExtensionGithubApi { } fun getApkUrl(extension: Extension.Available): String { - return "${REPO_URL_PREFIX}apk/${extension.apkName}" + return "${getUrlPrefix()}apk/${extension.apkName}" + } + + private fun getUrlPrefix(): String { + return if (requiresFallbackSource) { + FALLBACK_REPO_URL_PREFIX + } else { + REPO_URL_PREFIX + } } } private const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/" +private const val FALLBACK_REPO_URL_PREFIX = "https://cdn.jsdelivr.net/gh/tachiyomiorg/tachiyomi-extensions@repo/" @Serializable private data class ExtensionJsonObject( From 010436e7974d12e59e38e927d3652a9fb77cdc7f Mon Sep 17 00:00:00 2001 From: kasperskier <95685115+kasperskier@users.noreply.github.com> Date: Wed, 18 May 2022 05:19:17 +0800 Subject: [PATCH 23/44] Change jsDelivr CDN URL to Fastly (#7156) (cherry picked from commit 7b242bf11833ebd6dda34df295dfa7cd45cb88d0) --- .../eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt index 6869bbae9..92bae0dcb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt @@ -129,7 +129,7 @@ internal class ExtensionGithubApi { } private const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/" -private const val FALLBACK_REPO_URL_PREFIX = "https://cdn.jsdelivr.net/gh/tachiyomiorg/tachiyomi-extensions@repo/" +private const val FALLBACK_REPO_URL_PREFIX = "https://fastly.jsdelivr.net/gh/tachiyomiorg/tachiyomi-extensions@repo/" @Serializable private data class ExtensionJsonObject( From 5979e72662e01c8f2055fabca460a8ad8389c247 Mon Sep 17 00:00:00 2001 From: CVIUS <84634607+CVIUS@users.noreply.github.com> Date: Wed, 18 May 2022 05:20:18 +0800 Subject: [PATCH 24/44] Fix webtoon viewer showing transition view when going to next/prev chapter using next/prev button (#7133) (cherry picked from commit b21bcc2d45859ea86d6042b5df9d7c6f30d259cc) --- .../kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt index c88b405a6..3a21049dd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt @@ -245,7 +245,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr logcat { "moveToPage" } val position = adapter.items.indexOf(page) if (position != -1) { - recycler.scrollToPosition(position) + layoutManager.scrollToPositionWithOffset(position, 0) if (layoutManager.findLastEndVisibleItemPosition() == -1) { onScrolled(pos = position) } From a58a4634e2aae6e9cc395956ecf083fc22191ad3 Mon Sep 17 00:00:00 2001 From: CVIUS <84634607+CVIUS@users.noreply.github.com> Date: Wed, 18 May 2022 05:20:37 +0800 Subject: [PATCH 25/44] Fix reader menu appearing then disappearing in webtoon viewer when there is no next chapter (#7115) (cherry picked from commit 6580f5771f634b0e2c25f8cd42fa1596b2ea4e1c) --- .../tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt index 3a21049dd..9ec03ea6d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt @@ -103,6 +103,12 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr activity.requestPreloadChapter(firstItem.to) } } + + val lastIndex = layoutManager.findLastEndVisibleItemPosition() + val lastItem = adapter.items.getOrNull(lastIndex) + if (lastItem is ChapterTransition.Next && lastItem.to == null) { + activity.showMenu() + } } }, ) @@ -216,9 +222,6 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr if (toChapter != null) { logcat { "Request preload destination chapter because we're on the transition" } activity.requestPreloadChapter(toChapter) - } else if (transition is ChapterTransition.Next) { - // No more chapters, show menu because the user is probably going to close the reader - activity.showMenu() } } From 071dd88ef850c806d10748448ef9e5ff30c61db3 Mon Sep 17 00:00:00 2001 From: CVIUS <84634607+CVIUS@users.noreply.github.com> Date: Thu, 19 May 2022 10:35:27 +0800 Subject: [PATCH 26/44] Add ability to show manga when clicking item in migration search process (#7134) (cherry picked from commit bbb69482e1e2e39c62502a3caf03d13c59b01252) --- .../tachiyomi/ui/browse/migration/search/SearchController.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt index 0c103d7fe..da481b72a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt @@ -129,7 +129,10 @@ class SearchController( } (targetController as? SearchController)?.copyManga(manga, newManga) } - .setNeutralButton(android.R.string.cancel, null) + .setNeutralButton(activity?.getString(R.string.action_show_manga)) { _, _ -> + dismissDialog() + router.pushController(MangaController(newManga)) + } .create() } } From 940409a4c3d0a23d4e0c64333d63cb3e6deaa336 Mon Sep 17 00:00:00 2001 From: FourTOne5 <59261191+FourTOne5@users.noreply.github.com> Date: Wed, 25 May 2022 04:02:02 +0600 Subject: [PATCH 27/44] Local Source - qol, cleanup and cover related fixes (#7166) * Local Source - qol, cleanup and cover related fixes * Review Changes (cherry picked from commit ad17eb138609d684fd5929c3cb7dc644e3a3ec95) --- .../eu/kanade/tachiyomi/source/LocalSource.kt | 332 +++++++++++------- .../tachiyomi/ui/reader/ReaderPresenter.kt | 23 +- 2 files changed, 209 insertions(+), 146 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt index 6404ec135..a4ecd52d6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt @@ -1,8 +1,11 @@ package eu.kanade.tachiyomi.source import android.content.Context +import androidx.core.net.toUri import com.github.junrar.Archive +import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage @@ -30,6 +33,8 @@ import logcat.LogPriority import rx.Observable import tachiyomi.source.model.ChapterInfo import tachiyomi.source.model.MangaInfo +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.io.File import java.io.FileInputStream @@ -37,130 +42,104 @@ import java.io.InputStream import java.util.concurrent.TimeUnit import java.util.zip.ZipFile -class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSource { - - companion object { - const val ID = 0L - const val HELP_URL = "https://tachiyomi.org/help/guides/local-manga/" - - private const val COVER_NAME = "cover.jpg" - private val LATEST_THRESHOLD = TimeUnit.MILLISECONDS.convert(7, TimeUnit.DAYS) - - fun updateCover(context: Context, manga: SManga, input: InputStream): File? { - val dir = getBaseDirectories(context).firstOrNull() - if (dir == null) { - input.close() - return null - } - var cover = getCoverFile(File("${dir.absolutePath}/${manga.url}")) - if (cover == null) { - cover = File("${dir.absolutePath}/${manga.url}", COVER_NAME) - } - // It might not exist if using the external SD card - cover.parentFile?.mkdirs() - input.use { - cover.outputStream().use { - input.copyTo(it) - } - } - manga.thumbnail_url = cover.absolutePath - return cover - } - - /** - * Returns valid cover file inside [parent] directory. - */ - private fun getCoverFile(parent: File): File? { - return parent.listFiles()?.find { it.nameWithoutExtension == "cover" }?.takeIf { - it.isFile && ImageUtil.isImage(it.name) { it.inputStream() } - } - } - - private fun getBaseDirectories(context: Context): List { - val c = context.getString(R.string.app_name) + File.separator + "local" - return DiskUtil.getExternalStorages(context).map { File(it.absolutePath, c) } - } - } +class LocalSource( + private val context: Context, + private val coverCache: CoverCache = Injekt.get(), +) : CatalogueSource, UnmeteredSource { private val json: Json by injectLazy() - override val id = ID - override val name = context.getString(R.string.local_source) - override val lang = "other" - override val supportsLatest = true + override val name: String = context.getString(R.string.local_source) + + override val id: Long = ID + + override val lang: String = "other" override fun toString() = name + override val supportsLatest: Boolean = true + + // Browse related override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", POPULAR_FILTERS) - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - val baseDirs = getBaseDirectories(context) + override fun fetchLatestUpdates(page: Int) = fetchSearchManga(page, "", LATEST_FILTERS) - val time = if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L - var mangaDirs = baseDirs - .asSequence() - .mapNotNull { it.listFiles()?.toList() } - .flatten() - .filter { it.isDirectory } - .filterNot { it.name.startsWith('.') } - .filter { if (time == 0L) it.name.contains(query, ignoreCase = true) else it.lastModified() >= time } + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + val baseDirsFiles = getBaseDirectoriesFiles(context) + + var mangaDirs = baseDirsFiles + // Filter out files that are hidden and is not a folder + .filter { it.isDirectory && !it.name.startsWith('.') } .distinctBy { it.name } - val state = ((if (filters.isEmpty()) POPULAR_FILTERS else filters)[0] as OrderBy).state - when (state?.index) { - 0 -> { - mangaDirs = if (state.ascending) { - mangaDirs.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, { it.name })) - } else { - mangaDirs.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER, { it.name })) - } - } - 1 -> { - mangaDirs = if (state.ascending) { - mangaDirs.sortedBy(File::lastModified) - } else { - mangaDirs.sortedByDescending(File::lastModified) - } + val lastModifiedLimit = if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L + // Filter by query or last modified + mangaDirs = mangaDirs.filter { + if (lastModifiedLimit == 0L) { + it.name.contains(query, ignoreCase = true) + } else { + it.lastModified() >= lastModifiedLimit } } + filters.forEach { filter -> + when (filter) { + is OrderBy -> { + when (filter.state!!.index) { + 0 -> { + mangaDirs = if (filter.state!!.ascending) { + mangaDirs.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name }) + } else { + mangaDirs.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name }) + } + } + 1 -> { + mangaDirs = if (filter.state!!.ascending) { + mangaDirs.sortedBy(File::lastModified) + } else { + mangaDirs.sortedByDescending(File::lastModified) + } + } + } + } + + else -> { /* Do nothing */ } + } + } + + // Transform mangaDirs to list of SManga val mangas = mangaDirs.map { mangaDir -> SManga.create().apply { title = mangaDir.name url = mangaDir.name // Try to find the cover - for (dir in baseDirs) { - val cover = getCoverFile(File("${dir.absolutePath}/$url")) - if (cover != null && cover.exists()) { - thumbnail_url = cover.absolutePath - break - } + val cover = getCoverFile(mangaDir.name, baseDirsFiles) + if (cover != null && cover.exists()) { + thumbnail_url = cover.absolutePath } + } + } - val sManga = this - val mangaInfo = this.toMangaInfo() - runBlocking { - val chapters = getChapterList(mangaInfo) - if (chapters.isNotEmpty()) { - val chapter = chapters.last().toSChapter() - val format = getFormat(chapter) - if (format is Format.Epub) { - EpubFile(format.file).use { epub -> - epub.fillMangaMetadata(sManga) - } - } + // Fetch chapters of all the manga + mangas.forEach { manga -> + val mangaInfo = manga.toMangaInfo() + runBlocking { + val chapters = getChapterList(mangaInfo) + if (chapters.isNotEmpty()) { + val chapter = chapters.last().toSChapter() + val format = getFormat(chapter) - // Copy the cover from the first chapter found. - if (thumbnail_url == null) { - try { - val dest = updateCover(chapter, sManga) - thumbnail_url = dest?.absolutePath - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - } + if (format is Format.Epub) { + EpubFile(format.file).use { epub -> + epub.fillMangaMetadata(manga) } } + + // Copy the cover from the first chapter found if not available + if (manga.thumbnail_url == null) { + updateCover(chapter, manga) + } } } } @@ -168,38 +147,44 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour return Observable.just(MangasPage(mangas.toList(), false)) } - override fun fetchLatestUpdates(page: Int) = fetchSearchManga(page, "", LATEST_FILTERS) - + // Manga details related override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo { - val localDetails = getBaseDirectories(context) - .asSequence() - .mapNotNull { File(it, manga.key).listFiles()?.toList() } - .flatten() + var mangaInfo = manga + + val baseDirsFile = getBaseDirectoriesFiles(context) + + val coverFile = getCoverFile(manga.key, baseDirsFile) + + coverFile?.let { + mangaInfo = mangaInfo.copy(cover = it.absolutePath) + } + + val localDetails = getMangaDirsFiles(manga.key, baseDirsFile) .firstOrNull { it.extension.equals("json", ignoreCase = true) } - return if (localDetails != null) { + if (localDetails != null) { val obj = json.decodeFromStream(localDetails.inputStream()) - manga.copy( - title = obj["title"]?.jsonPrimitive?.contentOrNull ?: manga.title, - author = obj["author"]?.jsonPrimitive?.contentOrNull ?: manga.author, - artist = obj["artist"]?.jsonPrimitive?.contentOrNull ?: manga.artist, - description = obj["description"]?.jsonPrimitive?.contentOrNull ?: manga.description, - genres = obj["genre"]?.jsonArray?.map { it.jsonPrimitive.content } ?: manga.genres, - status = obj["status"]?.jsonPrimitive?.intOrNull ?: manga.status, + mangaInfo = mangaInfo.copy( + title = obj["title"]?.jsonPrimitive?.contentOrNull ?: mangaInfo.title, + author = obj["author"]?.jsonPrimitive?.contentOrNull ?: mangaInfo.author, + artist = obj["artist"]?.jsonPrimitive?.contentOrNull ?: mangaInfo.artist, + description = obj["description"]?.jsonPrimitive?.contentOrNull ?: mangaInfo.description, + genres = obj["genre"]?.jsonArray?.map { it.jsonPrimitive.content } ?: mangaInfo.genres, + status = obj["status"]?.jsonPrimitive?.intOrNull ?: mangaInfo.status, ) - } else { - manga } + + return mangaInfo } + // Chapters override suspend fun getChapterList(manga: MangaInfo): List { val sManga = manga.toSManga() - val chapters = getBaseDirectories(context) - .asSequence() - .mapNotNull { File(it, manga.key).listFiles()?.toList() } - .flatten() + val baseDirsFile = getBaseDirectoriesFiles(context) + return getMangaDirsFiles(manga.key, baseDirsFile) + // Only keep supported formats .filter { it.isDirectory || isSupportedFile(it.extension) } .map { chapterFile -> SChapter.create().apply { @@ -211,14 +196,14 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour } date_upload = chapterFile.lastModified() + ChapterRecognition.parseChapterNumber(this, sManga) + val format = getFormat(chapterFile) if (format is Format.Epub) { EpubFile(format.file).use { epub -> epub.fillChapterMetadata(this) } } - - ChapterRecognition.parseChapterNumber(this, sManga) } } .map { it.toChapterInfo() } @@ -227,12 +212,24 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c } .toList() - - return chapters } - override suspend fun getPageList(chapter: ChapterInfo) = throw Exception("Unused") + // Filters + override fun getFilterList() = FilterList(OrderBy(context)) + private val POPULAR_FILTERS = FilterList(OrderBy(context)) + private val LATEST_FILTERS = FilterList(OrderBy(context).apply { state = Filter.Sort.Selection(1, false) }) + + private class OrderBy(context: Context) : Filter.Sort( + context.getString(R.string.local_filter_order_by), + arrayOf(context.getString(R.string.title), context.getString(R.string.date)), + Selection(0, true), + ) + + // Unused stuff + override suspend fun getPageList(chapter: ChapterInfo) = throw UnsupportedOperationException("Unused") + + // Miscellaneous private fun isSupportedFile(extension: String): Boolean { return extension.lowercase() in SUPPORTED_ARCHIVE_TYPES } @@ -296,25 +293,90 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour } } } + .also { coverCache.clearMemoryCache() } } - override fun getFilterList() = POPULAR_FILTERS - - private val POPULAR_FILTERS = FilterList(OrderBy(context)) - private val LATEST_FILTERS = FilterList(OrderBy(context).apply { state = Filter.Sort.Selection(1, false) }) - - private class OrderBy(context: Context) : Filter.Sort( - context.getString(R.string.local_filter_order_by), - arrayOf(context.getString(R.string.title), context.getString(R.string.date)), - Selection(0, true), - ) - sealed class Format { data class Directory(val file: File) : Format() data class Zip(val file: File) : Format() data class Rar(val file: File) : Format() data class Epub(val file: File) : Format() } + + companion object { + const val ID = 0L + const val HELP_URL = "https://tachiyomi.org/help/guides/local-manga/" + + private const val DEFAULT_COVER_NAME = "cover.jpg" + private val LATEST_THRESHOLD = TimeUnit.MILLISECONDS.convert(7, TimeUnit.DAYS) + + private fun getBaseDirectories(context: Context): Sequence { + val localFolder = context.getString(R.string.app_name) + File.separator + "local" + return DiskUtil.getExternalStorages(context) + .map { File(it.absolutePath, localFolder) } + .asSequence() + } + + private fun getBaseDirectoriesFiles(context: Context): Sequence { + return getBaseDirectories(context) + // Get all the files inside all baseDir + .flatMap { it.listFiles().orEmpty().toList() } + } + + private fun getMangaDir(mangaUrl: String, baseDirsFile: Sequence): File? { + return baseDirsFile + // Get the first mangaDir or null + .firstOrNull { it.isDirectory && it.name == mangaUrl } + } + + private fun getMangaDirsFiles(mangaUrl: String, baseDirsFile: Sequence): Sequence { + return baseDirsFile + // Filter out ones that are not related to the manga and is not a directory + .filter { it.isDirectory && it.name == mangaUrl } + // Get all the files inside the filtered folders + .flatMap { it.listFiles().orEmpty().toList() } + } + + private fun getCoverFile(mangaUrl: String, baseDirsFile: Sequence): File? { + return getMangaDirsFiles(mangaUrl, baseDirsFile) + // Get all file whose names start with 'cover' + .filter { it.isFile && it.nameWithoutExtension.equals("cover", ignoreCase = true) } + // Get the first actual image + .firstOrNull { + ImageUtil.isImage(it.name) { it.inputStream() } + } + } + + fun updateCover(context: Context, manga: SManga, inputStream: InputStream): File? { + val baseDirsFiles = getBaseDirectoriesFiles(context) + + val mangaDir = getMangaDir(manga.url, baseDirsFiles) + if (mangaDir == null) { + inputStream.close() + return null + } + + var coverFile = getCoverFile(manga.url, baseDirsFiles) + if (coverFile == null) { + coverFile = File(mangaDir.absolutePath, DEFAULT_COVER_NAME) + + } + + // It might not exist at this point + coverFile.parentFile?.mkdirs() + inputStream.use { input -> + coverFile.outputStream().use { output -> + input.copyTo(output) + } + } + + // Create a .nomedia file + DiskUtil.createNoMediaFile(UniFile.fromFile(mangaDir), context) + + manga.thumbnail_url = coverFile.absolutePath + return coverFile + } + } } private val SUPPORTED_ARCHIVE_TYPES = listOf("zip", "cbz", "rar", "cbr", "epub") diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index 7a66b2fcb..a68a840ee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -4,7 +4,6 @@ import android.app.Application import android.net.Uri import android.os.Bundle import com.jakewharton.rxrelay.BehaviorRelay -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.History @@ -675,20 +674,22 @@ class ReaderPresenter( Observable .fromCallable { - if (manga.isLocal()) { - val context = Injekt.get() - LocalSource.updateCover(context, manga, stream()) - manga.updateCoverLastModified(db) - R.string.cover_updated - SetAsCoverResult.Success - } else { - if (manga.favorite) { - coverCache.setCustomCoverToCache(manga, stream()) + stream().use { + if (manga.isLocal()) { + val context = Injekt.get() + LocalSource.updateCover(context, manga, it) manga.updateCoverLastModified(db) coverCache.clearMemoryCache() SetAsCoverResult.Success } else { - SetAsCoverResult.AddToLibraryFirst + if (manga.favorite) { + coverCache.setCustomCoverToCache(manga, it) + manga.updateCoverLastModified(db) + coverCache.clearMemoryCache() + SetAsCoverResult.Success + } else { + SetAsCoverResult.AddToLibraryFirst + } } } } From c76a136d3f11aff50c15f1e25555f24fe300f15f Mon Sep 17 00:00:00 2001 From: Chris <52449218+shadow578@users.noreply.github.com> Date: Sat, 28 May 2022 15:09:53 +0200 Subject: [PATCH 28/44] Fix global update ignoring network constraint (#7188) * update library update network constraint logic * add explicit 'only on unmetered network' update constraint (cherry picked from commit 63238b388d1af3a0036f1d9a43cb4d2e87aabf5e) --- .../tachiyomi/data/library/LibraryUpdateJob.kt | 17 +++++------------ .../data/preference/PreferenceValues.kt | 1 + .../ui/setting/SettingsLibraryController.kt | 13 ++++--------- app/src/main/res/values/strings.xml | 1 + 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt index be2c51ab2..43f8d538d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt @@ -8,10 +8,7 @@ import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager import androidx.work.Worker import androidx.work.WorkerParameters -import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW -import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING -import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI -import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.* import eu.kanade.tachiyomi.util.system.isConnectedToWifi import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -22,8 +19,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet override fun doWork(): Result { val preferences = Injekt.get() - if (requiresWifiConnection(preferences) && !context.isConnectedToWifi()) { - Result.failure() + val restrictions = preferences.libraryUpdateDeviceRestriction().get() + if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) { + return Result.failure() } return if (LibraryUpdateService.start(context)) { @@ -42,7 +40,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet if (interval > 0) { val restrictions = preferences.libraryUpdateDeviceRestriction().get() val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) + .setRequiredNetworkType(if (DEVICE_NETWORK_NOT_METERED in restrictions) { NetworkType.UNMETERED } else { NetworkType.CONNECTED }) .setRequiresCharging(DEVICE_CHARGING in restrictions) .setRequiresBatteryNotLow(DEVICE_BATTERY_NOT_LOW in restrictions) .build() @@ -62,10 +60,5 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet WorkManager.getInstance(context).cancelAllWorkByTag(TAG) } } - - fun requiresWifiConnection(preferences: PreferencesHelper): Boolean { - val restrictions = preferences.libraryUpdateDeviceRestriction().get() - return DEVICE_ONLY_ON_WIFI in restrictions - } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt index 28cadea0c..ecff434c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.preference import eu.kanade.tachiyomi.R const val DEVICE_ONLY_ON_WIFI = "wifi" +const val DEVICE_NETWORK_NOT_METERED = "network_not_metered" const val DEVICE_CHARGING = "ac" const val DEVICE_BATTERY_NOT_LOW = "battery_not_low" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt index 70511823c..f0c7a4af7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt @@ -11,13 +11,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.library.LibraryUpdateJob -import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW -import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING -import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI -import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD -import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED -import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ -import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.* import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.databinding.PrefLibraryColumnsBinding import eu.kanade.tachiyomi.ui.base.controller.DialogController @@ -160,8 +154,8 @@ class SettingsLibraryController : SettingsController() { multiSelectListPreference { bindTo(preferences.libraryUpdateDeviceRestriction()) titleRes = R.string.pref_library_update_restriction - entriesRes = arrayOf(R.string.connected_to_wifi, R.string.charging, R.string.battery_not_low) - entryValues = arrayOf(DEVICE_ONLY_ON_WIFI, DEVICE_CHARGING, DEVICE_BATTERY_NOT_LOW) + entriesRes = arrayOf(R.string.connected_to_wifi, R.string.network_not_metered, R.string.charging, R.string.battery_not_low) + entryValues = arrayOf(DEVICE_ONLY_ON_WIFI, DEVICE_NETWORK_NOT_METERED, DEVICE_CHARGING, DEVICE_BATTERY_NOT_LOW) visibleIf(preferences.libraryUpdateInterval()) { it > 0 } @@ -177,6 +171,7 @@ class SettingsLibraryController : SettingsController() { .map { when (it) { DEVICE_ONLY_ON_WIFI -> context.getString(R.string.connected_to_wifi) + DEVICE_NETWORK_NOT_METERED -> context.getString(R.string.network_not_metered) DEVICE_CHARGING -> context.getString(R.string.charging) DEVICE_BATTERY_NOT_LOW -> context.getString(R.string.battery_not_low) else -> it diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 47cea213b..1b75f2657 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -222,6 +222,7 @@ Weekly Automatic updates device restrictions Only on Wi-Fi + Only on unmetered Network Charging Battery not low Restrictions: %s From 96d2fb62e47d8d44bf5be6e3470fdddec5051b90 Mon Sep 17 00:00:00 2001 From: kasperskier <95685115+kasperskier@users.noreply.github.com> Date: Sun, 5 Jun 2022 00:48:18 +0800 Subject: [PATCH 29/44] ChapterSourceSync: set default timestamp to max timestamp (#7197) (cherry picked from commit dd5da56695d8787ce5cf154b5d83aafaadf49def) --- .../kanade/tachiyomi/util/chapter/ChapterSourceSync.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt index 162b919e4..2efeeb295 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt @@ -11,6 +11,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.Date import java.util.TreeSet +import kotlin.math.max /** * Helper method for syncing the list of chapters from the source with the ones from the database. @@ -59,6 +60,9 @@ fun syncChaptersWithSource( } } + var maxTimestamp = 0L // in previous chapters to add + val rightNow = Date().time + for (sourceChapter in sourceChapters) { // This forces metadata update for the main viewable things in the chapter list. if (source is HttpSource) { @@ -72,7 +76,9 @@ fun syncChaptersWithSource( // Add the chapter if not in db already, or update if the metadata changed. if (dbChapter == null) { if (sourceChapter.date_upload == 0L) { - sourceChapter.date_upload = Date().time + sourceChapter.date_upload = if (maxTimestamp == 0L) rightNow else maxTimestamp + } else { + maxTimestamp = max(maxTimestamp, sourceChapter.date_upload) } toAdd.add(sourceChapter) } else { @@ -97,6 +103,7 @@ fun syncChaptersWithSource( return Pair(emptyList(), emptyList()) } + // Keep it a List instead of a Set. See #6372. val readded = mutableListOf() db.inTransaction { @@ -154,6 +161,7 @@ fun syncChaptersWithSource( db.updateLastUpdated(manga).executeAsBlocking() } + @Suppress("ConvertArgumentToSet") return Pair(toAdd.subtract(readded).toList(), toDelete.subtract(readded).toList()) } From 49c7dd0cacfe53bfd90ac3caa3e293821bfafe8b Mon Sep 17 00:00:00 2001 From: kasperskier <95685115+kasperskier@users.noreply.github.com> Date: Wed, 8 Jun 2022 05:58:58 +0800 Subject: [PATCH 30/44] Add more DoH providers (#7256) * Add more DoH providers * Fix IPs (cherry picked from commit 18ea6c4f655cfde314eb6b6d8061af313ba3f78b) --- .../kanade/tachiyomi/network/DohProviders.kt | 52 +++++++++++++++++++ .../kanade/tachiyomi/network/NetworkHelper.kt | 4 ++ .../ui/setting/SettingsAdvancedController.kt | 12 +++++ 3 files changed, 68 insertions(+) diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/DohProviders.kt b/app/src/main/java/eu/kanade/tachiyomi/network/DohProviders.kt index 9a783495c..f5ab389d6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/DohProviders.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/DohProviders.kt @@ -13,6 +13,10 @@ const val PREF_DOH_CLOUDFLARE = 1 const val PREF_DOH_GOOGLE = 2 const val PREF_DOH_ADGUARD = 3 const val PREF_DOH_QUAD9 = 4 +const val PREF_DOH_ALIDNS = 5 +const val PREF_DOH_DNSPOD = 6 +const val PREF_DOH_360 = 7 +const val PREF_DOH_QUAD101 = 8 fun OkHttpClient.Builder.dohCloudflare() = dns( DnsOverHttps.Builder().client(build()) @@ -68,3 +72,51 @@ fun OkHttpClient.Builder.dohQuad9() = dns( ) .build(), ) + +fun OkHttpClient.Builder.dohAliDNS() = dns( + DnsOverHttps.Builder().client(build()) + .url("https://dns.alidns.com/dns-query".toHttpUrl()) + .bootstrapDnsHosts( + InetAddress.getByName("223.5.5.5"), + InetAddress.getByName("223.6.6.6"), + InetAddress.getByName("2400:3200::1"), + InetAddress.getByName("2400:3200:baba::1"), + ) + .build(), +) + +fun OkHttpClient.Builder.dohDNSPod() = dns( + DnsOverHttps.Builder().client(build()) + .url("https://doh.pub/dns-query".toHttpUrl()) + .bootstrapDnsHosts( + InetAddress.getByName("1.12.12.12"), + InetAddress.getByName("120.53.53.53"), + ) + .build(), +) + +fun OkHttpClient.Builder.doh360() = dns( + DnsOverHttps.Builder().client(build()) + .url("https://doh.360.cn/dns-query".toHttpUrl()) + .bootstrapDnsHosts( + InetAddress.getByName("101.226.4.6"), + InetAddress.getByName("218.30.118.6"), + InetAddress.getByName("123.125.81.6"), + InetAddress.getByName("140.207.198.6"), + InetAddress.getByName("180.163.249.75"), + InetAddress.getByName("101.199.113.208"), + InetAddress.getByName("36.99.170.86"), + ) + .build(), +) + +fun OkHttpClient.Builder.dohQuad101() = dns( + DnsOverHttps.Builder().client(build()) + .url("https://dns.twnic.tw/dns-query".toHttpUrl()) + .bootstrapDnsHosts( + InetAddress.getByName("101.101.101.101"), + InetAddress.getByName("2001:de4::101"), + InetAddress.getByName("2001:de4::102"), + ) + .build(), +) diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt index c59056daf..ea3106cb8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -43,6 +43,10 @@ class NetworkHelper(context: Context) { PREF_DOH_GOOGLE -> builder.dohGoogle() PREF_DOH_ADGUARD -> builder.dohAdGuard() PREF_DOH_QUAD9 -> builder.dohQuad9() + PREF_DOH_ALIDNS -> builder.dohAliDNS() + PREF_DOH_DNSPOD -> builder.dohDNSPod() + PREF_DOH_360 -> builder.doh360() + PREF_DOH_QUAD101 -> builder.dohQuad101() } return builder diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt index 594ae3894..666058b92 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt @@ -16,9 +16,13 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Target import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.network.PREF_DOH_360 import eu.kanade.tachiyomi.network.PREF_DOH_ADGUARD +import eu.kanade.tachiyomi.network.PREF_DOH_ALIDNS import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE +import eu.kanade.tachiyomi.network.PREF_DOH_DNSPOD import eu.kanade.tachiyomi.network.PREF_DOH_GOOGLE +import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101 import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9 import eu.kanade.tachiyomi.ui.base.controller.openInBrowser import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction @@ -182,6 +186,10 @@ class SettingsAdvancedController : SettingsController() { "Google", "AdGuard", "Quad9", + "AliDNS", + "DNSPod", + "360", + "Quad 101", ) entryValues = arrayOf( "-1", @@ -189,6 +197,10 @@ class SettingsAdvancedController : SettingsController() { PREF_DOH_GOOGLE.toString(), PREF_DOH_ADGUARD.toString(), PREF_DOH_QUAD9.toString(), + PREF_DOH_ALIDNS.toString(), + PREF_DOH_DNSPOD.toString(), + PREF_DOH_360.toString(), + PREF_DOH_QUAD101.toString(), ) defaultValue = "-1" summary = "%s" From 83e93b254e9e6a1a5af550b9d354cf5163c27632 Mon Sep 17 00:00:00 2001 From: arkon Date: Wed, 8 Jun 2022 22:31:01 -0400 Subject: [PATCH 31/44] Don't show clipboard copy confirmation toast on Android 13 or above (cherry picked from commit 40f5d26945a61de0a6bea27bcee53e7b536174c6) --- .../eu/kanade/tachiyomi/util/system/ContextExtensions.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index 134d8470d..d9a76d31e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -87,7 +87,11 @@ fun Context.copyToClipboard(label: String, content: String) { val clipboard = getSystemService()!! clipboard.setPrimaryClip(ClipData.newPlainText(label, content)) - toast(getString(R.string.copied_to_clipboard, content.truncateCenter(50))) + // Android 13 and higher shows a visual confirmation of copied contents + // https://developer.android.com/about/versions/13/features/copy-paste + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + toast(getString(R.string.copied_to_clipboard, content.truncateCenter(50))) + } } catch (e: Throwable) { logcat(LogPriority.ERROR, e) toast(R.string.clipboard_copy_error) From 5bc4a446ec78aa4bf8fc5352c0ae7a1d08065955 Mon Sep 17 00:00:00 2001 From: arkon Date: Tue, 14 Jun 2022 22:31:24 -0400 Subject: [PATCH 32/44] Fix wrapped long page numbers in reader (closes #7300) (cherry picked from commit 6bc484617ee382b1ac8262e75671e535647d3bc4) --- app/src/main/res/layout/reader_activity.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/reader_activity.xml b/app/src/main/res/layout/reader_activity.xml index 243590e1c..9305bdfb9 100644 --- a/app/src/main/res/layout/reader_activity.xml +++ b/app/src/main/res/layout/reader_activity.xml @@ -95,9 +95,10 @@ @@ -116,9 +117,10 @@ From af82591d8595ea1255bae53cd6ac8e72cd3ccc99 Mon Sep 17 00:00:00 2001 From: arkon Date: Mon, 20 Jun 2022 22:51:34 -0400 Subject: [PATCH 33/44] Fix accented UI elements in library sheet being different colors (cherry picked from commit cd5bcc36734e0959569be6dd5ecd3b3813863207) --- .../java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt index f1dfe7f12..c27abfad1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt @@ -73,7 +73,7 @@ open class ExtendedNavigationView @JvmOverloads constructor( * @param context any context. * @param resId the vector resource to load and tint */ - fun tintVector(context: Context, resId: Int, @AttrRes colorAttrRes: Int = R.attr.colorAccent): Drawable { + fun tintVector(context: Context, resId: Int, @AttrRes colorAttrRes: Int = R.attr.colorPrimary): Drawable { return AppCompatResources.getDrawable(context, resId)!!.apply { setTint(context.getResourceColor(if (enabled) colorAttrRes else R.attr.colorControlNormal)) } From 29ced9642d63df956f07904011b19e4b272091d4 Mon Sep 17 00:00:00 2001 From: jobobby04 Date: Sat, 25 Jun 2022 11:09:41 -0400 Subject: [PATCH 34/44] Fix downloader crash related to UnmeteredSource (#7365) Fix crash when starting a download with chaqpters from a UnmeteredSource (cherry picked from commit 470a5764417ccd63a274ccea0e483a12ec1adbda) --- .../main/java/eu/kanade/tachiyomi/data/download/Downloader.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index e2f84f646..732a1c2bc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -278,7 +278,8 @@ class Downloader( val maxDownloadsFromSource = queue .groupBy { it.source } .filterKeys { it !is UnmeteredSource } - .maxOf { it.value.size } + .maxOfOrNull { it.value.size } + ?: 0 if ( queuedDownloads > DOWNLOADS_QUEUED_WARNING_THRESHOLD || maxDownloadsFromSource > CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD From 4c3eb68d3ac2779d4f1a60d6d833d0f90e0461e9 Mon Sep 17 00:00:00 2001 From: arkon Date: Sat, 25 Jun 2022 22:34:48 -0400 Subject: [PATCH 35/44] Use primary color for excluded tristate filter icon (fixes #7360) (cherry picked from commit 3ca1ce463696e2e8eabf63c1e729dbdd2ffe1f71) --- .../tachiyomi/ui/browse/source/filter/TriStateItem.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/filter/TriStateItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/filter/TriStateItem.kt index 2eed3e7a8..04e4e1edc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/filter/TriStateItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/filter/TriStateItem.kt @@ -42,10 +42,10 @@ open class TriStateItem(val filter: Filter.TriState) : AbstractFlexibleItem throw Exception("Unknown state") }, )?.apply { - val color = if (filter.state == Filter.TriState.STATE_INCLUDE) { - view.context.getResourceColor(R.attr.colorAccent) - } else { + val color = if (filter.state == Filter.TriState.STATE_IGNORE) { view.context.getResourceColor(R.attr.colorOnBackground, 0.38f) + } else { + view.context.getResourceColor(R.attr.colorPrimary) } setTint(color) From 17899a6d6d3a494135f96202452571f0cbdd80a2 Mon Sep 17 00:00:00 2001 From: Osyx <9387558+Osyx@users.noreply.github.com> Date: Sun, 26 Jun 2022 16:01:31 +0200 Subject: [PATCH 36/44] Add new "Lavender" theme (#7343) * Add new "Lavender" theme * Add light theme values for Lavender theme * Fix order of enums * Fix accented UI elements in set categories sheet being different colors Co-authored-by: CrepeTF (cherry picked from commit ad106bd8842dfc9c047c0412b92a0cb1dc1aba1a) --- .../data/preference/PreferenceValues.kt | 5 ++- .../ui/base/delegate/ThemingDelegate.kt | 3 ++ .../materialdialogs/QuadStateTextView.kt | 2 +- .../main/res/values-night/color_lavender.xml | 37 +++++++++++++++++++ app/src/main/res/values/color_lavender.xml | 36 ++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/themes.xml | 28 ++++++++++++++ 7 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/values-night/color_lavender.xml create mode 100644 app/src/main/res/values/color_lavender.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt index ecff434c9..c5965588e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt @@ -30,13 +30,14 @@ object PreferenceValues { enum class AppTheme(val titleResId: Int?) { DEFAULT(R.string.label_default), MONET(R.string.theme_monet), + GREEN_APPLE(R.string.theme_greenapple), + LAVENDER(R.string.theme_lavender), MIDNIGHT_DUSK(R.string.theme_midnightdusk), STRAWBERRY_DAIQUIRI(R.string.theme_strawberrydaiquiri), - YOTSUBA(R.string.theme_yotsuba), TAKO(R.string.theme_tako), - GREEN_APPLE(R.string.theme_greenapple), TEALTURQUOISE(R.string.theme_tealturquoise), YINYANG(R.string.theme_yinyang), + YOTSUBA(R.string.theme_yotsuba), // Deprecated DARK_BLUE(null), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt index c3d74a15c..f3e54385a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt @@ -20,6 +20,9 @@ interface ThemingDelegate { PreferenceValues.AppTheme.GREEN_APPLE -> { resIds += R.style.Theme_Tachiyomi_GreenApple } + PreferenceValues.AppTheme.LAVENDER -> { + resIds += R.style.Theme_Tachiyomi_Lavender + } PreferenceValues.AppTheme.MIDNIGHT_DUSK -> { resIds += R.style.Theme_Tachiyomi_MidnightDusk } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateTextView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateTextView.kt index b6b21fcca..e8e9fe77f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateTextView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateTextView.kt @@ -29,7 +29,7 @@ class QuadStateTextView @JvmOverloads constructor(context: Context, attrs: Attri val tint = if (state == State.UNCHECKED) { context.getThemeColor(R.attr.colorControlNormal) } else { - context.getThemeColor(R.attr.colorAccent) + context.getThemeColor(R.attr.colorPrimary) } if (tint != 0) { TextViewCompat.setCompoundDrawableTintList(this, ColorStateList.valueOf(tint)) diff --git a/app/src/main/res/values-night/color_lavender.xml b/app/src/main/res/values-night/color_lavender.xml new file mode 100644 index 000000000..d76e55486 --- /dev/null +++ b/app/src/main/res/values-night/color_lavender.xml @@ -0,0 +1,37 @@ + + + + #A177FF + #111129 + #A177FF + #111129 + #A177FF + #111129 + #A177FF + #111129 + #5E25E1 + #E8E8E8 + #111129 + #DEE8FF + #111129 + #DEE8FF + #111129 + #DEE8FF + #2CB6B6B6 + #E8E8E8 + #A8905FFF + #DEE8FF + #221247 + #A177FF + @color/lavender_primary + diff --git a/app/src/main/res/values/color_lavender.xml b/app/src/main/res/values/color_lavender.xml new file mode 100644 index 000000000..f6cfda65c --- /dev/null +++ b/app/src/main/res/values/color_lavender.xml @@ -0,0 +1,36 @@ + + + + #7B46AF + #EDE2FF + #7B46AF + #EDE2FF + #7B46AF + #EDE2FF + #7B46AF + #EDE2FF + #EDE2FF + #7B46AF + #EDE2FF + #7B46AF + #EDE2FF + #1B1B22 + #EDE2FF + #1B1B22 + #B9B0CC + #D849454E + #7B46AF + #F3EFF4 + #313033 + #D6BAFF + @color/lavender_primary + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1b75f2657..2632544b8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -157,6 +157,7 @@ App theme Dynamic Green Apple + Lavender Midnight Dusk Strawberry Daiquiri Tako diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 81a1c64cb..67867d06f 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -121,6 +121,34 @@ @color/greenapple_primaryInverse + + +