Downsample offscreen pages option
This commit is contained in:
@@ -134,7 +134,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'io.coil-kt:coil-base:2.5.0'
|
implementation 'io.coil-kt:coil-base:2.5.0'
|
||||||
implementation 'io.coil-kt:coil-svg:2.5.0'
|
implementation 'io.coil-kt:coil-svg:2.5.0'
|
||||||
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:2bcc9791b1'
|
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:0fef1a47c9'
|
||||||
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
||||||
implementation 'io.noties.markwon:core:4.6.2'
|
implementation 'io.noties.markwon:core:4.6.2'
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
val isReaderTapsAdaptive: Boolean
|
val isReaderTapsAdaptive: Boolean
|
||||||
get() = !prefs.getBoolean(KEY_READER_TAPS_LTR, false)
|
get() = !prefs.getBoolean(KEY_READER_TAPS_LTR, false)
|
||||||
|
|
||||||
|
val isReaderOptimizationEnabled: Boolean
|
||||||
|
get() = prefs.getBoolean(KEY_READER_OPTIMIZE, false)
|
||||||
|
|
||||||
var isTrafficWarningEnabled: Boolean
|
var isTrafficWarningEnabled: Boolean
|
||||||
get() = prefs.getBoolean(KEY_TRAFFIC_WARNING, true)
|
get() = prefs.getBoolean(KEY_TRAFFIC_WARNING, true)
|
||||||
set(value) = prefs.edit { putBoolean(KEY_TRAFFIC_WARNING, value) }
|
set(value) = prefs.edit { putBoolean(KEY_TRAFFIC_WARNING, value) }
|
||||||
@@ -506,6 +509,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
const val KEY_READER_SCREEN_ON = "reader_screen_on"
|
const val KEY_READER_SCREEN_ON = "reader_screen_on"
|
||||||
const val KEY_SHORTCUTS = "dynamic_shortcuts"
|
const val KEY_SHORTCUTS = "dynamic_shortcuts"
|
||||||
const val KEY_READER_TAPS_LTR = "reader_taps_ltr"
|
const val KEY_READER_TAPS_LTR = "reader_taps_ltr"
|
||||||
|
const val KEY_READER_OPTIMIZE = "reader_optimize"
|
||||||
const val KEY_LOCAL_LIST_ORDER = "local_order"
|
const val KEY_LOCAL_LIST_ORDER = "local_order"
|
||||||
const val KEY_HISTORY_ORDER = "history_order"
|
const val KEY_HISTORY_ORDER = "history_order"
|
||||||
const val KEY_WEBTOON_ZOOM = "webtoon_zoom"
|
const val KEY_WEBTOON_ZOOM = "webtoon_zoom"
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package org.koitharu.kotatsu.core.ui.list.lifecycle
|
||||||
|
|
||||||
|
import androidx.core.view.children
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
|
||||||
|
|
||||||
|
class RecyclerViewLifecycleDispatcher : RecyclerView.OnScrollListener() {
|
||||||
|
|
||||||
|
private var prevFirst = NO_POSITION
|
||||||
|
private var prevLast = NO_POSITION
|
||||||
|
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
val lm = recyclerView.layoutManager as? LinearLayoutManager ?: return
|
||||||
|
val first = lm.findFirstVisibleItemPosition()
|
||||||
|
val last = lm.findLastVisibleItemPosition()
|
||||||
|
if (first == prevFirst && last == prevLast) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
prevFirst = first
|
||||||
|
prevLast = last
|
||||||
|
if (first == NO_POSITION || last == NO_POSITION) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (child in recyclerView.children) {
|
||||||
|
val wh = recyclerView.getChildViewHolder(child) ?: continue
|
||||||
|
(wh as? LifecycleAwareViewHolder)?.setIsCurrent(wh.absoluteAdapterPosition in first..last)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,9 @@ class ReaderSettings(
|
|||||||
val colorFilter: ReaderColorFilter?
|
val colorFilter: ReaderColorFilter?
|
||||||
get() = colorFilterFlow.value?.takeUnless { it.isEmpty }
|
get() = colorFilterFlow.value?.takeUnless { it.isEmpty }
|
||||||
|
|
||||||
|
val isReaderOptimizationEnabled: Boolean
|
||||||
|
get() = settings.isReaderOptimizationEnabled
|
||||||
|
|
||||||
val bitmapConfig: Bitmap.Config
|
val bitmapConfig: Bitmap.Config
|
||||||
get() = if (settings.is32BitColorsEnabled) {
|
get() = if (settings.is32BitColorsEnabled) {
|
||||||
Bitmap.Config.ARGB_8888
|
Bitmap.Config.ARGB_8888
|
||||||
@@ -104,7 +107,8 @@ class ReaderSettings(
|
|||||||
key == AppSettings.KEY_ZOOM_MODE ||
|
key == AppSettings.KEY_ZOOM_MODE ||
|
||||||
key == AppSettings.KEY_PAGES_NUMBERS ||
|
key == AppSettings.KEY_PAGES_NUMBERS ||
|
||||||
key == AppSettings.KEY_READER_BACKGROUND ||
|
key == AppSettings.KEY_READER_BACKGROUND ||
|
||||||
key == AppSettings.KEY_32BIT_COLOR
|
key == AppSettings.KEY_32BIT_COLOR ||
|
||||||
|
key == AppSettings.KEY_READER_OPTIMIZE
|
||||||
) {
|
) {
|
||||||
notifyChanged()
|
notifyChanged()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.viewbinding.ViewBinding
|
|||||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||||
import org.koitharu.kotatsu.core.os.NetworkState
|
import org.koitharu.kotatsu.core.os.NetworkState
|
||||||
import org.koitharu.kotatsu.core.ui.list.lifecycle.LifecycleAwareViewHolder
|
import org.koitharu.kotatsu.core.ui.list.lifecycle.LifecycleAwareViewHolder
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.isLowRamDevice
|
||||||
import org.koitharu.kotatsu.databinding.LayoutPageInfoBinding
|
import org.koitharu.kotatsu.databinding.LayoutPageInfoBinding
|
||||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||||
@@ -67,4 +68,10 @@ abstract class BasePageHolder<B : ViewBinding>(
|
|||||||
open fun onRecycled() {
|
open fun onRecycled() {
|
||||||
delegate.onRecycle()
|
delegate.onRecycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun getBackgroundDownsampling() = when {
|
||||||
|
!settings.isReaderOptimizationEnabled -> 1
|
||||||
|
context.isLowRamDevice() -> 8
|
||||||
|
else -> 4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ class PageHolderDelegate(
|
|||||||
private var error: Throwable? = null
|
private var error: Throwable? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
callback.onConfigChanged()
|
scope.launch(Dispatchers.Main) { // the same as post() -- wait until child fields init
|
||||||
|
callback.onConfigChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isLoading() = job?.isActive == true
|
fun isLoading() = job?.isActive == true
|
||||||
|
|||||||
@@ -45,12 +45,22 @@ open class PageHolder(
|
|||||||
binding.textViewNumber.isVisible = settings.isPagesNumbersEnabled
|
binding.textViewNumber.isVisible = settings.isPagesNumbersEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
binding.ssiv.downsampling = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
binding.ssiv.downsampling = getBackgroundDownsampling()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onConfigChanged() {
|
override fun onConfigChanged() {
|
||||||
super.onConfigChanged()
|
super.onConfigChanged()
|
||||||
@Suppress("SENSELESS_COMPARISON")
|
if (settings.applyBitmapConfig(binding.ssiv)) {
|
||||||
if (settings.applyBitmapConfig(binding.ssiv) && delegate != null) {
|
|
||||||
delegate.reload()
|
delegate.reload()
|
||||||
}
|
}
|
||||||
|
binding.ssiv.downsampling = if (isResumed()) 1 else getBackgroundDownsampling()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
|
|||||||
@@ -40,10 +40,20 @@ class WebtoonHolder(
|
|||||||
|
|
||||||
override fun onConfigChanged() {
|
override fun onConfigChanged() {
|
||||||
super.onConfigChanged()
|
super.onConfigChanged()
|
||||||
@Suppress("SENSELESS_COMPARISON")
|
if (settings.applyBitmapConfig(binding.ssiv)) {
|
||||||
if (settings.applyBitmapConfig(binding.ssiv) && delegate != null) {
|
|
||||||
delegate.reload()
|
delegate.reload()
|
||||||
}
|
}
|
||||||
|
binding.ssiv.downsampling = if (isResumed()) 1 else getBackgroundDownsampling()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
binding.ssiv.downsampling = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
binding.ssiv.downsampling = getBackgroundDownsampling()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(data: ReaderPage) {
|
override fun onBind(data: ReaderPage) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import kotlinx.coroutines.coroutineScope
|
|||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.os.NetworkState
|
import org.koitharu.kotatsu.core.os.NetworkState
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.lifecycle.RecyclerViewLifecycleDispatcher
|
||||||
import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition
|
import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition
|
||||||
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
|
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
@@ -44,6 +45,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
|||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
adapter = readerAdapter
|
adapter = readerAdapter
|
||||||
addOnPageScrollListener(PageScrollListener())
|
addOnPageScrollListener(PageScrollListener())
|
||||||
|
addOnScrollListener(RecyclerViewLifecycleDispatcher())
|
||||||
}
|
}
|
||||||
viewModel.isWebtoonZooEnabled.observe(viewLifecycleOwner) {
|
viewModel.isWebtoonZooEnabled.observe(viewLifecycleOwner) {
|
||||||
binding.frame.isZoomEnable = it
|
binding.frame.isZoomEnable = it
|
||||||
|
|||||||
@@ -527,4 +527,6 @@
|
|||||||
<string name="available_d">Available: %1$d</string>
|
<string name="available_d">Available: %1$d</string>
|
||||||
<string name="disable_nsfw_summary">Disable NSFW sources and hide adult manga from list if possible</string>
|
<string name="disable_nsfw_summary">Disable NSFW sources and hide adult manga from list if possible</string>
|
||||||
<string name="state_paused">Paused</string>
|
<string name="state_paused">Paused</string>
|
||||||
|
<string name="reader_optimize">Reduce memory consumption (beta)</string>
|
||||||
|
<string name="reader_optimize_summary">Reduce offscreen pages quality to use less memory</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -60,6 +60,12 @@
|
|||||||
android:summary="@string/enhanced_colors_summary"
|
android:summary="@string/enhanced_colors_summary"
|
||||||
android:title="@string/enhanced_colors" />
|
android:title="@string/enhanced_colors" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="reader_optimize"
|
||||||
|
android:summary="@string/reader_optimize_summary"
|
||||||
|
android:title="@string/reader_optimize" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="reader_bar"
|
android:key="reader_bar"
|
||||||
|
|||||||
Reference in New Issue
Block a user