feat(webtoon): Adaptive scale for wider screen size (#1130)
* feat: Add webtoon scale type option This commit introduces a new setting for webtoon reading mode that allows users to select a scale type for webtoon images. The available scale types are: - Fit Screen - 16:9 - 20:9 - 4:3 - 3:2 This setting can be found in both the reader settings sheet and the global reader settings. * mutable * add to Reader's settings page & fix size calculation * option for long strip gap preference * fix: ensure recycler scales to default when screen ratio is less than desired * hide smart scale for long strip gap preference * Avoid zero division * using forEach * improve readability * better viewer size calculation & optimize list
This commit is contained in:
parent
9f7309fd2a
commit
a0e2d2942a
7 changed files with 121 additions and 19 deletions
|
|
@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences.Companion.zoomWideImagesAllowedList
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences.Companion.zoomWideImagesAllowedList
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences.WebtoonScaleType
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -258,8 +259,10 @@ object SettingsReaderScreen : SearchableSettings {
|
||||||
val rotateToFit by rotateToFitPref.collectAsState()
|
val rotateToFit by rotateToFitPref.collectAsState()
|
||||||
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
val pagedDisableZoomIn by readerPreferences.pagedDisableZoomIn().collectAsState()
|
val pagedDisableZoomInPref = readerPreferences.pagedDisableZoomIn()
|
||||||
val landscapeZoom by readerPreferences.landscapeZoom().collectAsState()
|
val landscapeZoomPref = readerPreferences.landscapeZoom()
|
||||||
|
val pagedDisableZoomIn by pagedDisableZoomInPref.collectAsState()
|
||||||
|
val landscapeZoom by landscapeZoomPref.collectAsState()
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
|
|
@ -313,7 +316,7 @@ object SettingsReaderScreen : SearchableSettings {
|
||||||
),
|
),
|
||||||
// SY <--
|
// SY <--
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = readerPreferences.landscapeZoom(),
|
preference = landscapeZoomPref,
|
||||||
title = stringResource(MR.strings.pref_landscape_zoom),
|
title = stringResource(MR.strings.pref_landscape_zoom),
|
||||||
// KMK -->
|
// KMK -->
|
||||||
enabled = imageScaleType in zoomWideImagesAllowedList,
|
enabled = imageScaleType in zoomWideImagesAllowedList,
|
||||||
|
|
@ -322,17 +325,14 @@ object SettingsReaderScreen : SearchableSettings {
|
||||||
// KMK -->
|
// KMK -->
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
preference = readerPreferences.landscapeZoomType(),
|
preference = readerPreferences.landscapeZoomType(),
|
||||||
entries = persistentListOf(
|
entries = ReaderPreferences.LandscapeZoomScaleType.entries
|
||||||
ReaderPreferences.LandscapeZoomScaleType.FIT,
|
|
||||||
ReaderPreferences.LandscapeZoomScaleType.DOUBLE,
|
|
||||||
)
|
|
||||||
.associateWith { stringResource(it.titleRes) }
|
.associateWith { stringResource(it.titleRes) }
|
||||||
.toImmutableMap(),
|
.toImmutableMap(),
|
||||||
title = stringResource(KMR.strings.pref_landscape_zoom_type),
|
title = stringResource(KMR.strings.pref_landscape_zoom_type),
|
||||||
enabled = landscapeZoom && imageScaleType in zoomWideImagesAllowedList,
|
enabled = landscapeZoom && imageScaleType in zoomWideImagesAllowedList,
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = readerPreferences.pagedDisableZoomIn(),
|
preference = pagedDisableZoomInPref,
|
||||||
title = stringResource(KMR.strings.pref_paged_disable_zoom_in),
|
title = stringResource(KMR.strings.pref_paged_disable_zoom_in),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
|
@ -415,6 +415,19 @@ object SettingsReaderScreen : SearchableSettings {
|
||||||
title = stringResource(MR.strings.pref_read_with_tapping_inverted),
|
title = stringResource(MR.strings.pref_read_with_tapping_inverted),
|
||||||
enabled = navMode != 5,
|
enabled = navMode != 5,
|
||||||
),
|
),
|
||||||
|
// KMK -->
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
preference = readerPreferences.webtoonScaleType(),
|
||||||
|
entries = WebtoonScaleType.entries
|
||||||
|
.associateWith { stringResource(it.titleRes) }
|
||||||
|
.toImmutableMap(),
|
||||||
|
title = stringResource(KMR.strings.pref_webtoon_scale_type),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
preference = readerPreferences.longStripGapSmartScale(),
|
||||||
|
title = stringResource(KMR.strings.pref_smart_scale_long_strip_gap),
|
||||||
|
),
|
||||||
|
// KMK <--
|
||||||
Preference.PreferenceItem.SliderPreference(
|
Preference.PreferenceItem.SliderPreference(
|
||||||
value = webtoonSidePadding,
|
value = webtoonSidePadding,
|
||||||
valueRange = ReaderPreferences.let {
|
valueRange = ReaderPreferences.let {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
|
||||||
import eu.kanade.domain.manga.model.readerOrientation
|
import eu.kanade.domain.manga.model.readerOrientation
|
||||||
import eu.kanade.domain.manga.model.readingMode
|
import eu.kanade.domain.manga.model.readingMode
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
|
|
@ -55,7 +54,12 @@ internal fun ReadingModePage(screenModel: ReaderSettingsScreenModel) {
|
||||||
|
|
||||||
val viewer by screenModel.viewerFlow.collectAsState()
|
val viewer by screenModel.viewerFlow.collectAsState()
|
||||||
if (viewer is WebtoonViewer) {
|
if (viewer is WebtoonViewer) {
|
||||||
WebtoonViewerSettings(screenModel)
|
WebtoonViewerSettings(
|
||||||
|
screenModel,
|
||||||
|
// KMK -->
|
||||||
|
readingMode,
|
||||||
|
// KMK <--
|
||||||
|
)
|
||||||
// SY -->
|
// SY -->
|
||||||
WebtoonWithGapsViewerSettings(screenModel)
|
WebtoonWithGapsViewerSettings(screenModel)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
@ -125,11 +129,7 @@ private fun PagerViewerSettings(screenModel: ReaderSettingsScreenModel) {
|
||||||
)
|
)
|
||||||
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
if (imageScaleType in listOf(
|
if (imageScaleType in ReaderPreferences.zoomWideImagesAllowedList) {
|
||||||
SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE,
|
|
||||||
SubsamplingScaleImageView.SCALE_TYPE_ORIGINAL_SIZE,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// KMK <--
|
// KMK <--
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_landscape_zoom),
|
label = stringResource(MR.strings.pref_landscape_zoom),
|
||||||
|
|
@ -207,7 +207,12 @@ private fun PagerViewerSettings(screenModel: ReaderSettingsScreenModel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun WebtoonViewerSettings(screenModel: ReaderSettingsScreenModel) {
|
private fun WebtoonViewerSettings(
|
||||||
|
screenModel: ReaderSettingsScreenModel,
|
||||||
|
// KMK -->
|
||||||
|
readingMode: ReadingMode,
|
||||||
|
// KMK <--
|
||||||
|
) {
|
||||||
val numberFormat = remember { NumberFormat.getPercentInstance() }
|
val numberFormat = remember { NumberFormat.getPercentInstance() }
|
||||||
|
|
||||||
HeadingItem(MR.strings.webtoon_viewer)
|
HeadingItem(MR.strings.webtoon_viewer)
|
||||||
|
|
@ -221,6 +226,23 @@ private fun WebtoonViewerSettings(screenModel: ReaderSettingsScreenModel) {
|
||||||
onSelectInvertMode = screenModel.preferences.webtoonNavInverted()::set,
|
onSelectInvertMode = screenModel.preferences.webtoonNavInverted()::set,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// KMK -->
|
||||||
|
val webtoonScaleTypePref = screenModel.preferences.webtoonScaleType()
|
||||||
|
val webtoonScaleType by webtoonScaleTypePref.collectAsState()
|
||||||
|
val webtoonSmartScaleLongStripGap = screenModel.preferences.longStripGapSmartScale().get()
|
||||||
|
if (readingMode != ReadingMode.CONTINUOUS_VERTICAL || webtoonSmartScaleLongStripGap) {
|
||||||
|
SettingsChipRow(KMR.strings.pref_webtoon_scale_type) {
|
||||||
|
ReaderPreferences.WebtoonScaleType.entries.forEach { scaleType ->
|
||||||
|
FilterChip(
|
||||||
|
selected = webtoonScaleType == scaleType,
|
||||||
|
onClick = { webtoonScaleTypePref.set(scaleType) },
|
||||||
|
label = { Text(stringResource(scaleType.titleRes)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
|
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = webtoonSidePadding,
|
value = webtoonSidePadding,
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,11 @@ class ReaderPreferences(
|
||||||
|
|
||||||
fun imageScaleType() = preferenceStore.getInt("pref_image_scale_type_key", 1)
|
fun imageScaleType() = preferenceStore.getInt("pref_image_scale_type_key", 1)
|
||||||
|
|
||||||
|
// KMK -->
|
||||||
|
fun webtoonScaleType() = preferenceStore.getEnum("pref_webtoon_scale_type_key", WebtoonScaleType.FIT)
|
||||||
|
fun longStripGapSmartScale() = preferenceStore.getBoolean("pref_webtoon_smart_scale_long_strip_gap", false)
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
fun zoomStart() = preferenceStore.getInt("pref_zoom_start_key", 1)
|
fun zoomStart() = preferenceStore.getInt("pref_zoom_start_key", 1)
|
||||||
|
|
||||||
fun readerTheme() = preferenceStore.getInt("pref_reader_theme_key", 1)
|
fun readerTheme() = preferenceStore.getInt("pref_reader_theme_key", 1)
|
||||||
|
|
@ -237,6 +242,17 @@ class ReaderPreferences(
|
||||||
FIT(MR.strings.scale_type_fit_screen),
|
FIT(MR.strings.scale_type_fit_screen),
|
||||||
DOUBLE(KMR.strings.scale_type_double),
|
DOUBLE(KMR.strings.scale_type_double),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class WebtoonScaleType(
|
||||||
|
val titleRes: StringResource,
|
||||||
|
val ratio: Float,
|
||||||
|
) {
|
||||||
|
FIT(MR.strings.scale_type_fit_screen, 0f),
|
||||||
|
R4_3(KMR.strings.scale_type_4_3, 3f / 4f),
|
||||||
|
R3_2(KMR.strings.scale_type_3_2, 2f / 3f),
|
||||||
|
R16_9(KMR.strings.scale_type_16_9, 9f / 16f),
|
||||||
|
R20_9(KMR.strings.scale_type_20_9, 9f / 20f),
|
||||||
|
}
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
||||||
object ArchiveReaderMode {
|
object ArchiveReaderMode {
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,11 @@ class WebtoonConfig(
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var pinchToZoomChangedListener: ((Boolean) -> Unit)? = null
|
var pinchToZoomChangedListener: ((Boolean) -> Unit)? = null
|
||||||
|
|
||||||
|
var webtoonScaleType = readerPreferences.webtoonScaleType().get()
|
||||||
|
private set
|
||||||
|
|
||||||
|
var webtoonScaleTypeChangedListener: ((ReaderPreferences.WebtoonScaleType) -> Unit)? = null
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|
@ -115,6 +120,12 @@ class WebtoonConfig(
|
||||||
{ pinchToZoom = it },
|
{ pinchToZoom = it },
|
||||||
{ pinchToZoomChangedListener?.invoke(it) },
|
{ pinchToZoomChangedListener?.invoke(it) },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
readerPreferences.webtoonScaleType()
|
||||||
|
.register(
|
||||||
|
{ webtoonScaleType = it },
|
||||||
|
{ webtoonScaleTypeChangedListener?.invoke(it) },
|
||||||
|
)
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
||||||
readerPreferences.readerTheme().changes()
|
readerPreferences.readerTheme().changes()
|
||||||
|
|
|
||||||
|
|
@ -180,9 +180,13 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
||||||
fun onScale(scaleFactor: Float) {
|
fun onScale(scaleFactor: Float) {
|
||||||
// KMK ->
|
// KMK ->
|
||||||
if (!detector.isQuickScaling && !pinchToZoom) return
|
if (!detector.isQuickScaling && !pinchToZoom) return
|
||||||
// KMK <--
|
|
||||||
|
|
||||||
currentScale *= scaleFactor
|
scaleTo(currentScale * scaleFactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scaleTo(scale: Float) {
|
||||||
|
// KMK <--
|
||||||
|
currentScale = scale
|
||||||
currentScale = currentScale.coerceIn(
|
currentScale = currentScale.coerceIn(
|
||||||
minRate,
|
minRate,
|
||||||
MAX_SCALE_RATE,
|
MAX_SCALE_RATE,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation.NavigationRegion
|
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation.NavigationRegion
|
||||||
|
import exh.util.nullIfZero
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
|
|
@ -40,6 +41,7 @@ class WebtoonViewer(
|
||||||
private val tapByPage: Boolean = false,
|
private val tapByPage: Boolean = false,
|
||||||
// KMK -->
|
// KMK -->
|
||||||
@ColorInt private val seedColor: Int? = null,
|
@ColorInt private val seedColor: Int? = null,
|
||||||
|
private val readerPreferences: ReaderPreferences = Injekt.get(),
|
||||||
// KMK <--
|
// KMK <--
|
||||||
) : Viewer {
|
) : Viewer {
|
||||||
|
|
||||||
|
|
@ -89,7 +91,9 @@ class WebtoonViewer(
|
||||||
var currentPage: Any? = null
|
var currentPage: Any? = null
|
||||||
|
|
||||||
private val threshold: Int =
|
private val threshold: Int =
|
||||||
Injekt.get<ReaderPreferences>()
|
// KMK -->
|
||||||
|
readerPreferences
|
||||||
|
// KMK <--
|
||||||
.readerHideThreshold()
|
.readerHideThreshold()
|
||||||
.get()
|
.get()
|
||||||
.threshold
|
.threshold
|
||||||
|
|
@ -173,6 +177,32 @@ class WebtoonViewer(
|
||||||
config.pinchToZoomChangedListener = {
|
config.pinchToZoomChangedListener = {
|
||||||
frame.pinchToZoom = it
|
frame.pinchToZoom = it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.webtoonScaleTypeChangedListener = f@{ scaleType ->
|
||||||
|
if (!isContinuous && !readerPreferences.longStripGapSmartScale().get()) return@f
|
||||||
|
|
||||||
|
recycler.post {
|
||||||
|
if (scaleType == ReaderPreferences.WebtoonScaleType.FIT) {
|
||||||
|
recycler.scaleTo(1f)
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call `scaleTo` after the view is loaded and visible
|
||||||
|
val currentWidth = recycler.width.takeIf { it > 0 } ?: activity.window.decorView.width.nullIfZero() ?: return@post
|
||||||
|
val currentHeight = recycler.originalHeight.takeIf { it > 0 } ?: activity.window.decorView.height.nullIfZero() ?: return@post
|
||||||
|
|
||||||
|
val desiredRatio = scaleType.ratio
|
||||||
|
val screenRatio = currentWidth.toFloat() / currentHeight
|
||||||
|
val desiredWidth = currentHeight * desiredRatio
|
||||||
|
val desiredScale = desiredWidth / currentWidth
|
||||||
|
|
||||||
|
if (screenRatio > desiredRatio) {
|
||||||
|
recycler.scaleTo(desiredScale)
|
||||||
|
} else {
|
||||||
|
recycler.scaleTo(1f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
||||||
config.zoomPropertyChangedListener = {
|
config.zoomPropertyChangedListener = {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,12 @@
|
||||||
<string name="pref_paged_disable_zoom_in">Disable zoom in</string>
|
<string name="pref_paged_disable_zoom_in">Disable zoom in</string>
|
||||||
<string name="pref_landscape_zoom_type">Wide images zoom mode</string>
|
<string name="pref_landscape_zoom_type">Wide images zoom mode</string>
|
||||||
<string name="scale_type_double">Double zoom</string>
|
<string name="scale_type_double">Double zoom</string>
|
||||||
|
<string name="pref_webtoon_scale_type">Smart scale on wide screen</string>
|
||||||
|
<string name="pref_smart_scale_long_strip_gap">Apply Smart scale to Long strip with gaps</string>
|
||||||
|
<string name="scale_type_16_9">16:9</string>
|
||||||
|
<string name="scale_type_20_9">20:9</string>
|
||||||
|
<string name="scale_type_4_3">4:3</string>
|
||||||
|
<string name="scale_type_3_2">3:2</string>
|
||||||
|
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<!-- Appearance section -->
|
<!-- Appearance section -->
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue