From 95d7ca52643eb76eb7268f846afbe223dd24f1c0 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 12 May 2022 14:39:15 +0300 Subject: [PATCH] Configure default reader mode #160 #142 --- .../kotatsu/base/domain/MangaUtils.kt | 63 +++++++++---------- .../kotatsu/core/prefs/AppSettings.kt | 10 ++- .../kotatsu/reader/ui/ReaderViewModel.kt | 38 ++++++----- .../settings/AppearanceSettingsFragment.kt | 2 +- .../settings/ContentSettingsFragment.kt | 9 ++- .../settings/ReaderSettingsFragment.kt | 56 ++++++++++++++--- .../kotatsu/utils/ext/CollectionExt.kt | 4 -- app/src/main/res/values/arrays.xml | 5 ++ app/src/main/res/values/strings.xml | 5 +- app/src/main/res/xml/pref_reader.xml | 14 +++-- 10 files changed, 134 insertions(+), 72 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt index 6c481e467..b3b32dc1f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt @@ -3,9 +3,6 @@ package org.koitharu.kotatsu.base.domain import android.graphics.BitmapFactory import android.net.Uri import android.util.Size -import java.io.File -import java.io.InputStream -import java.util.zip.ZipFile import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runInterruptible import okhttp3.OkHttpClient @@ -16,46 +13,46 @@ import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.util.await -import org.koitharu.kotatsu.parsers.util.medianOrNull -import org.koitharu.kotatsu.utils.ext.printStackTraceDebug +import java.io.File +import java.io.InputStream +import java.util.zip.ZipFile +import kotlin.math.roundToInt object MangaUtils : KoinComponent { + private const val MIN_WEBTOON_RATIO = 2 + /** * Automatic determine type of manga by page size * @return ReaderMode.WEBTOON if page is wide */ - suspend fun determineMangaIsWebtoon(pages: List): Boolean? { - try { - val page = pages.medianOrNull() ?: return null - val url = MangaRepository(page.source).getPageUrl(page) - val uri = Uri.parse(url) - val size = if (uri.scheme == "cbz") { - runInterruptible(Dispatchers.IO) { - val zip = ZipFile(uri.schemeSpecificPart) - val entry = zip.getEntry(uri.fragment) - zip.getInputStream(entry).use { - getBitmapSize(it) - } - } - } else { - val request = Request.Builder() - .url(url) - .get() - .header(CommonHeaders.REFERER, page.referer) - .cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED) - .build() - get().newCall(request).await().use { - runInterruptible(Dispatchers.IO) { - getBitmapSize(it.body?.byteStream()) - } + suspend fun determineMangaIsWebtoon(pages: List): Boolean { + val pageIndex = (pages.size * 0.3).roundToInt() + val page = requireNotNull(pages.getOrNull(pageIndex)) { "No pages" } + val url = MangaRepository(page.source).getPageUrl(page) + val uri = Uri.parse(url) + val size = if (uri.scheme == "cbz") { + runInterruptible(Dispatchers.IO) { + val zip = ZipFile(uri.schemeSpecificPart) + val entry = zip.getEntry(uri.fragment) + zip.getInputStream(entry).use { + getBitmapSize(it) + } + } + } else { + val request = Request.Builder() + .url(url) + .get() + .header(CommonHeaders.REFERER, page.referer) + .cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED) + .build() + get().newCall(request).await().use { + runInterruptible(Dispatchers.IO) { + getBitmapSize(it.body?.byteStream()) } } - return size.width * 2 < size.height - } catch (e: Exception) { - e.printStackTraceDebug() - return null } + return size.width * MIN_WEBTOON_RATIO < size.height } suspend fun getImageMimeType(file: File): String? = runInterruptible(Dispatchers.IO) { diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 62e47f141..b81fcbe7a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -99,8 +99,11 @@ class AppSettings(context: Context) { val readerAnimation: Boolean get() = prefs.getBoolean(KEY_READER_ANIMATION, false) - val isPreferRtlReader: Boolean - get() = prefs.getBoolean(KEY_READER_PREFER_RTL, false) + val defaultReaderMode: ReaderMode + get() = prefs.getEnumValue(KEY_READER_MODE, ReaderMode.STANDARD) + + val isReaderModeDetectionEnabled: Boolean + get() = prefs.getBoolean(KEY_READER_MODE_DETECT, true) var historyGrouping: Boolean get() = prefs.getBoolean(KEY_HISTORY_GROUPING, true) @@ -287,7 +290,8 @@ class AppSettings(context: Context) { const val KEY_NOTIFICATIONS_LIGHT = "notifications_light" const val KEY_NOTIFICATIONS_INFO = "tracker_notifications_info" const val KEY_READER_ANIMATION = "reader_animation" - const val KEY_READER_PREFER_RTL = "reader_prefer_rtl" + const val KEY_READER_MODE = "reader_mode" + const val KEY_READER_MODE_DETECT = "reader_mode_detect" const val KEY_APP_PASSWORD = "app_password" const val KEY_PROTECT_APP = "protect_app" const val KEY_APP_VERSION = "app_version" diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index 784c844ff..931461de0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -6,6 +6,7 @@ import androidx.activity.result.ActivityResultLauncher import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope +import java.util.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import org.koitharu.kotatsu.R @@ -31,7 +32,6 @@ import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.printStackTraceDebug import org.koitharu.kotatsu.utils.ext.processLifecycleScope -import java.util.* private const val BOUNDS_PAGE_OFFSET = 2 private const val PAGES_TRIM_THRESHOLD = 120 @@ -264,15 +264,7 @@ class ReaderViewModel( chapters.put(it.id, it) } // determine mode - val mode = dataRepository.getReaderMode(manga.id) ?: manga.chapters?.randomOrNull()?.let { - val pages = repo.getPages(it) - val isWebtoon = MangaUtils.determineMangaIsWebtoon(pages) - val newMode = getReaderMode(isWebtoon) - if (isWebtoon != null) { - dataRepository.savePreferences(manga, newMode) - } - newMode - } ?: error("There are no chapters in this manga") + val mode = detectReaderMode(manga, repo) // obtain state if (currentState.value == null) { currentState.value = historyRepository.getOne(manga)?.let { @@ -295,12 +287,6 @@ class ReaderViewModel( } } - private fun getReaderMode(isWebtoon: Boolean?) = when { - isWebtoon == true -> ReaderMode.WEBTOON - settings.isPreferRtlReader -> ReaderMode.REVERSED - else -> ReaderMode.STANDARD - } - private suspend fun loadChapter(chapterId: Long): List { val manga = checkNotNull(mangaData.value) { "Manga is null" } val chapter = checkNotNull(chapters[chapterId]) { "Requested chapter not found" } @@ -358,6 +344,26 @@ class ReaderViewModel( subList(fromIndexBounded, toIndexBounded) } } + + private suspend fun detectReaderMode(manga: Manga, repo: MangaRepository): ReaderMode { + dataRepository.getReaderMode(manga.id)?.let { return it } + val defaultMode = settings.defaultReaderMode + if (!settings.isReaderModeDetectionEnabled || defaultMode == ReaderMode.WEBTOON) { + return defaultMode + } + val chapter = currentState.value?.chapterId?.let(chapters::get) + ?: manga.chapters?.randomOrNull() + ?: error("There are no chapters in this manga") + val pages = repo.getPages(chapter) + return runCatching { + val isWebtoon = MangaUtils.determineMangaIsWebtoon(pages) + if (isWebtoon) ReaderMode.WEBTOON else defaultMode + }.onSuccess { + dataRepository.savePreferences(manga, it) + }.onFailure { + it.printStackTraceDebug() + }.getOrDefault(defaultMode) + } } /** diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt index 36031ec21..81817e8b5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt @@ -12,9 +12,9 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.ListMode +import org.koitharu.kotatsu.parsers.util.names import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity import org.koitharu.kotatsu.settings.utils.SliderPreference -import org.koitharu.kotatsu.utils.ext.names import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat import java.util.* diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt index dab7b972e..800599441 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt @@ -13,9 +13,9 @@ import org.koitharu.kotatsu.base.ui.dialog.StorageSelectDialog import org.koitharu.kotatsu.core.network.DoHProvider import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.local.data.LocalStorageManager +import org.koitharu.kotatsu.parsers.util.names import org.koitharu.kotatsu.settings.utils.SliderPreference import org.koitharu.kotatsu.utils.ext.getStorageName -import org.koitharu.kotatsu.utils.ext.names import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat import org.koitharu.kotatsu.utils.ext.viewLifecycleScope import java.io.File @@ -41,7 +41,12 @@ class ContentSettingsFragment : } } findPreference(AppSettings.KEY_DOH)?.run { - entryValues = enumValues().names() + entryValues = arrayOf( + DoHProvider.NONE, + DoHProvider.GOOGLE, + DoHProvider.CLOUDFLARE, + DoHProvider.ADGUARD, + ).names() setDefaultValueCompat(DoHProvider.NONE.name) } bindRemoteSourcesSummary() diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt index 5602d23c2..39f4de6c5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt @@ -1,26 +1,68 @@ package org.koitharu.kotatsu.settings +import android.content.SharedPreferences import android.os.Bundle +import android.view.View import androidx.preference.ListPreference import androidx.preference.MultiSelectListPreference +import androidx.preference.Preference import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.ReaderMode +import org.koitharu.kotatsu.parsers.util.names import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider -import org.koitharu.kotatsu.utils.ext.names import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat -class ReaderSettingsFragment : BasePreferenceFragment(R.string.reader_settings) { +class ReaderSettingsFragment : + BasePreferenceFragment(R.string.reader_settings), + SharedPreferences.OnSharedPreferenceChangeListener { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_reader) - findPreference(AppSettings.KEY_READER_SWITCHERS)?.let { - it.summaryProvider = MultiSummaryProvider(R.string.gestures_only) + findPreference(AppSettings.KEY_READER_MODE)?.run { + entryValues = arrayOf( + ReaderMode.STANDARD, + ReaderMode.REVERSED, + ReaderMode.WEBTOON, + ).names() + setDefaultValueCompat(ReaderMode.STANDARD.name) } - findPreference(AppSettings.KEY_ZOOM_MODE)?.let { - it.entryValues = ZoomMode.values().names() - it.setDefaultValueCompat(ZoomMode.FIT_CENTER.name) + findPreference(AppSettings.KEY_READER_SWITCHERS)?.run { + summaryProvider = MultiSummaryProvider(R.string.gestures_only) + } + findPreference(AppSettings.KEY_ZOOM_MODE)?.run { + entryValues = arrayOf( + ZoomMode.FIT_CENTER, + ZoomMode.FIT_HEIGHT, + ZoomMode.FIT_WIDTH, + ZoomMode.KEEP_START, + ).names() + setDefaultValueCompat(ZoomMode.FIT_CENTER.name) + } + updateReaderModeDependency() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + settings.subscribe(this) + } + + override fun onDestroyView() { + settings.unsubscribe(this) + super.onDestroyView() + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + when (key) { + AppSettings.KEY_READER_MODE -> updateReaderModeDependency() + } + } + + private fun updateReaderModeDependency() { + findPreference(AppSettings.KEY_READER_MODE_DETECT)?.run { + isEnabled = settings.defaultReaderMode != ReaderMode.WEBTOON } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt index 04bc82e1d..0ab153da6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt @@ -3,10 +3,6 @@ package org.koitharu.kotatsu.utils.ext import androidx.collection.ArraySet import java.util.* -fun > Array.names() = Array(size) { i -> - this[i].name -} - fun MutableList.move(sourceIndex: Int, targetIndex: Int) { if (sourceIndex <= targetIndex) { Collections.rotate(subList(sourceIndex, targetIndex + 1), -1) diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 4f56771f0..176b70687 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -40,4 +40,9 @@ CloudFlare AdGuard + + @string/standard + @string/right_to_left + @string/webtoon + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d4f35e720..4c3749375 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -160,8 +160,6 @@ Could not look for updates No updates available Right-to-left (←) - Prefer right-to-left (←) reader - Reading mode can be set up separately for each series New category Scale mode Fit center @@ -296,4 +294,7 @@ Undo Removed from history DNS over HTTPS + Default mode + Autodetect reader mode + Automatically detect if manga is webtoon \ No newline at end of file diff --git a/app/src/main/res/xml/pref_reader.xml b/app/src/main/res/xml/pref_reader.xml index 309dba5cd..42e46b8ad 100644 --- a/app/src/main/res/xml/pref_reader.xml +++ b/app/src/main/res/xml/pref_reader.xml @@ -3,11 +3,17 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> + + + android:defaultValue="true" + android:key="reader_mode_detect" + android:summary="@string/detect_reader_mode_summary" + android:title="@string/detect_reader_mode" />