Scale mode option for reader

pull/26/head
Koitharu 6 years ago
parent bdebd0578e
commit 28a9659410

@ -16,7 +16,7 @@ android {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 30
versionCode gitCommits versionCode gitCommits
versionName '0.5.4' versionName '1.0-a1'
kapt { kapt {
arguments { arguments {
@ -63,8 +63,8 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
implementation 'androidx.core:core-ktx:1.5.0-alpha04' implementation 'androidx.core:core-ktx:1.5.0-alpha04'
implementation 'androidx.activity:activity-ktx:1.2.0-beta01' implementation 'androidx.activity:activity-ktx:1.2.0-beta01'

@ -0,0 +1,6 @@
package org.koitharu.kotatsu.core.model
enum class ZoomMode {
FIT_CENTER, FIT_HEIGHT, FIT_WIDTH, KEEP_START
}

@ -6,11 +6,13 @@ import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.domain.MangaLoaderContext
abstract class RemoteMangaRepository(protected val loaderContext: MangaLoaderContext) : MangaRepository { abstract class RemoteMangaRepository(
protected val loaderContext: MangaLoaderContext
) : MangaRepository {
protected abstract val source: MangaSource protected abstract val source: MangaSource
protected val conf by lazy(LazyThreadSafetyMode.NONE) { protected val conf by lazy {
loaderContext.getSettings(source) loaderContext.getSettings(source)
} }

@ -7,6 +7,7 @@ import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
import java.util.*
abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepository( abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepository(
loaderContext loaderContext
@ -14,7 +15,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
protected abstract val defaultDomain: String protected abstract val defaultDomain: String
override val sortOrders = arraySetOf( override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST, SortOrder.NEWEST,
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.ALPHABETICAL SortOrder.ALPHABETICAL

@ -7,12 +7,14 @@ import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
import java.util.*
import kotlin.collections.ArrayList
class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepository(loaderContext) { class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepository(loaderContext) {
override val source = MangaSource.DESUME override val source = MangaSource.DESUME
override val sortOrders = arraySetOf( override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED, SortOrder.UPDATED,
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.NEWEST, SortOrder.NEWEST,

@ -7,13 +7,14 @@ import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
import java.util.*
abstract class GroupleRepository(loaderContext: MangaLoaderContext) : abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
RemoteMangaRepository(loaderContext) { RemoteMangaRepository(loaderContext) {
protected abstract val defaultDomain: String protected abstract val defaultDomain: String
override val sortOrders = arraySetOf( override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.POPULARITY,
SortOrder.NEWEST, SortOrder.RATING SortOrder.NEWEST, SortOrder.RATING
//FIXME SortOrder.ALPHABETICAL //FIXME SortOrder.ALPHABETICAL

@ -4,13 +4,14 @@ import androidx.collection.ArraySet
import androidx.collection.arraySetOf import androidx.collection.arraySetOf
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
import java.util.*
import kotlin.collections.ArrayList
open class MangaLibRepository(loaderContext: MangaLoaderContext) : open class MangaLibRepository(loaderContext: MangaLoaderContext) :
RemoteMangaRepository(loaderContext) { RemoteMangaRepository(loaderContext) {
@ -19,7 +20,7 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
override val source = MangaSource.MANGALIB override val source = MangaSource.MANGALIB
override val sortOrders = arraySetOf( override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.RATING, SortOrder.RATING,
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,
SortOrder.POPULARITY, SortOrder.POPULARITY,

@ -15,7 +15,7 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
override val source = MangaSource.MANGATOWN override val source = MangaSource.MANGATOWN
override val sortOrders = arraySetOf( override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL,
SortOrder.RATING, SortOrder.RATING,
SortOrder.POPULARITY, SortOrder.POPULARITY,

@ -7,6 +7,7 @@ import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
import java.util.*
class MangareadRepository( class MangareadRepository(
loaderContext: MangaLoaderContext loaderContext: MangaLoaderContext
@ -14,7 +15,10 @@ class MangareadRepository(
override val source = MangaSource.MANGAREAD override val source = MangaSource.MANGAREAD
override val sortOrders = arraySetOf(SortOrder.UPDATED, SortOrder.POPULARITY) override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.POPULARITY
)
override suspend fun getList( override suspend fun getList(
offset: Int, offset: Int,

@ -7,13 +7,18 @@ import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.domain.MangaLoaderContext import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
class NudeMoonRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepository(loaderContext) { class NudeMoonRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepository(loaderContext) {
override val source = MangaSource.NUDEMOON override val source = MangaSource.NUDEMOON
override val sortOrders = arraySetOf(SortOrder.NEWEST, SortOrder.POPULARITY, SortOrder.RATING) override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST,
SortOrder.POPULARITY,
SortOrder.RATING
)
init { init {
loaderContext.insertCookies( loaderContext.insertCookies(
@ -35,9 +40,7 @@ class NudeMoonRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
tag != null -> "https://$domain/tags/${tag.key}&rowstart=$offset" tag != null -> "https://$domain/tags/${tag.key}&rowstart=$offset"
else -> "https://$domain/all_manga?${getSortKey(sortOrder)}&rowstart=$offset" else -> "https://$domain/all_manga?${getSortKey(sortOrder)}&rowstart=$offset"
} }
val doc = loaderContext.httpGet(url) { val doc = loaderContext.httpGet(url).parseHtml()
addHeader("Cookie", "NMfYa=1; nm_mobile=0;")
}.parseHtml()
val root = doc.body().run { val root = doc.body().run {
selectFirst("td.shoutbox") ?: selectFirst("td.main-bg") selectFirst("td.shoutbox") ?: selectFirst("td.main-bg")
} ?: throw ParseException("Cannot find root") } ?: throw ParseException("Cannot find root")

@ -7,6 +7,7 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.arraySetOf import androidx.collection.arraySetOf
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.parser.LocalMangaRepository import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koitharu.kotatsu.utils.delegates.prefs.* import org.koitharu.kotatsu.utils.delegates.prefs.*
import java.io.File import java.io.File
@ -18,13 +19,13 @@ class AppSettings private constructor(private val prefs: SharedPreferences) :
PreferenceManager.getDefaultSharedPreferences(context) PreferenceManager.getDefaultSharedPreferences(context)
) )
var listMode by EnumPreferenceDelegate( var listMode by IntEnumPreferenceDelegate(
ListMode::class.java, ListMode::class.java,
KEY_LIST_MODE, KEY_LIST_MODE,
ListMode.DETAILED_LIST ListMode.DETAILED_LIST
) )
var defaultSection by EnumPreferenceDelegate( var defaultSection by IntEnumPreferenceDelegate(
AppSection::class.java, AppSection::class.java,
KEY_APP_SECTION, KEY_APP_SECTION,
AppSection.HISTORY AppSection.HISTORY
@ -66,6 +67,12 @@ class AppSettings private constructor(private val prefs: SharedPreferences) :
val isPreferRtlReader by BoolPreferenceDelegate(KEY_READER_PREFER_RTL, false) val isPreferRtlReader by BoolPreferenceDelegate(KEY_READER_PREFER_RTL, false)
val zoomMode by EnumPreferenceDelegate(
ZoomMode::class.java,
KEY_ZOOM_MODE,
ZoomMode.FIT_CENTER
)
val trackSources by StringSetPreferenceDelegate( val trackSources by StringSetPreferenceDelegate(
KEY_TRACK_SOURCES, KEY_TRACK_SOURCES,
arraySetOf(TRACK_FAVOURITES, TRACK_HISTORY) arraySetOf(TRACK_FAVOURITES, TRACK_HISTORY)
@ -143,5 +150,6 @@ class AppSettings private constructor(private val prefs: SharedPreferences) :
const val KEY_APP_PASSWORD = "app_password" const val KEY_APP_PASSWORD = "app_password"
const val KEY_PROTECT_APP = "protect_app" const val KEY_PROTECT_APP = "protect_app"
const val KEY_APP_VERSION = "app_version" const val KEY_APP_VERSION = "app_version"
const val KEY_ZOOM_MODE = "zoom_mode"
} }
} }

@ -13,20 +13,16 @@ open class MangaLoaderContext : KoinComponent {
private val okHttp by inject<OkHttpClient>() private val okHttp by inject<OkHttpClient>()
private val cookieJar by inject<CookieJar>() private val cookieJar by inject<CookieJar>()
suspend fun httpGet(url: String, block: (Request.Builder.() -> Unit)? = null): Response { suspend fun httpGet(url: String): Response {
val request = Request.Builder() val request = Request.Builder()
.get() .get()
.url(url) .url(url)
if (block != null) {
request.block()
}
return okHttp.newCall(request.build()).await() return okHttp.newCall(request.build()).await()
} }
suspend fun httpPost( suspend fun httpPost(
url: String, url: String,
form: Map<String, String>, form: Map<String, String>
block: (Request.Builder.() -> Unit)? = null
): Response { ): Response {
val body = FormBody.Builder() val body = FormBody.Builder()
form.forEach { (k, v) -> form.forEach { (k, v) ->
@ -35,16 +31,12 @@ open class MangaLoaderContext : KoinComponent {
val request = Request.Builder() val request = Request.Builder()
.post(body.build()) .post(body.build())
.url(url) .url(url)
if (block != null) {
request.block()
}
return okHttp.newCall(request.build()).await() return okHttp.newCall(request.build()).await()
} }
suspend fun httpPost( suspend fun httpPost(
url: String, url: String,
payload: String, payload: String
block: (Request.Builder.() -> Unit)? = null
): Response { ): Response {
val body = FormBody.Builder() val body = FormBody.Builder()
payload.split('&').forEach { payload.split('&').forEach {
@ -58,9 +50,6 @@ open class MangaLoaderContext : KoinComponent {
val request = Request.Builder() val request = Request.Builder()
.post(body.build()) .post(body.build())
.url(url) .url(url)
if (block != null) {
request.block()
}
return okHttp.newCall(request.build()).await() return okHttp.newCall(request.build()).await()
} }

@ -1,5 +0,0 @@
package org.koitharu.kotatsu.ui.reader
enum class ReaderAction {
REPLACE, PREPEND, APPEND
}

@ -96,7 +96,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
ViewCompat.setOnApplyWindowInsetsListener(rootLayout, this) ViewCompat.setOnApplyWindowInsetsListener(rootLayout, this)
settings.subscribe(this) settings.subscribe(this)
loadSettings() loadSwitchSettings()
orientationHelper.observeAutoOrientation() orientationHelper.observeAutoOrientation()
.onEach { .onEach {
toolbar_bottom.menu.findItem(R.id.action_screen_rotate).isVisible = !it toolbar_bottom.menu.findItem(R.id.action_screen_rotate).isVisible = !it
@ -370,7 +370,11 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
loadSettings() when (key) {
AppSettings.KEY_READER_SWITCHERS -> loadSwitchSettings()
AppSettings.KEY_READER_ANIMATION,
AppSettings.KEY_ZOOM_MODE -> reader?.recreateAdapter()
}
} }
private fun showWaitWhileLoading() { private fun showWaitWhileLoading() {
@ -410,7 +414,7 @@ class ReaderActivity : BaseFullscreenActivity(), ReaderView, ChaptersDialog.OnCh
.build() .build()
} }
private fun loadSettings() { private fun loadSwitchSettings() {
settings.readerPageSwitch.let { settings.readerPageSwitch.let {
isTapSwitchEnabled = it.contains(AppSettings.PAGE_SWITCH_TAPS) isTapSwitchEnabled = it.contains(AppSettings.PAGE_SWITCH_TAPS)
isVolumeKeysSwitchEnabled = it.contains(AppSettings.PAGE_SWITCH_VOLUME_KEYS) isVolumeKeysSwitchEnabled = it.contains(AppSettings.PAGE_SWITCH_VOLUME_KEYS)

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.ui.reader.base
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.annotation.CallSuper
import androidx.collection.LongSparseArray import androidx.collection.LongSparseArray
import androidx.core.view.postDelayed import androidx.core.view.postDelayed
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
@ -131,6 +132,11 @@ abstract class AbstractReader(contentLayoutId: Int) : BaseFragment(contentLayout
super.onDestroy() super.onDestroy()
} }
@CallSuper
open fun recreateAdapter() {
adapter = onCreateAdapter(pages)
}
fun getPages(): List<MangaPage>? { fun getPages(): List<MangaPage>? {
val chapterId = (pages.getOrNull(getCurrentItem()) ?: return null).chapterId val chapterId = (pages.getOrNull(getCurrentItem()) ?: return null).chapterId
// TODO optimize // TODO optimize

@ -4,7 +4,10 @@ import android.net.Uri
import androidx.core.net.toUri import androidx.core.net.toUri
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.koin.core.component.inject
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.ui.reader.PageLoader import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.utils.ext.launchAfter import org.koitharu.kotatsu.utils.ext.launchAfter
import org.koitharu.kotatsu.utils.ext.launchInstead import org.koitharu.kotatsu.utils.ext.launchInstead
@ -14,8 +17,10 @@ import java.io.IOException
class PageHolderDelegate( class PageHolderDelegate(
private val loader: PageLoader, private val loader: PageLoader,
private val callback: Callback private val callback: Callback
) : SubsamplingScaleImageView.DefaultOnImageEventListener(), CoroutineScope by loader { ) : SubsamplingScaleImageView.DefaultOnImageEventListener(),
CoroutineScope by loader {
private val settings by loader.inject<AppSettings>()
private var state = State.EMPTY private var state = State.EMPTY
private var job: Job? = null private var job: Job? = null
private var file: File? = null private var file: File? = null
@ -36,7 +41,7 @@ class PageHolderDelegate(
override fun onReady() { override fun onReady() {
state = State.SHOWING state = State.SHOWING
callback.onImageShowing() callback.onImageShowing(settings.zoomMode)
} }
override fun onImageLoaded() { override fun onImageLoaded() {
@ -79,7 +84,7 @@ class PageHolderDelegate(
state = State.LOADED state = State.LOADED
callback.onImageReady(file.toUri()) callback.onImageReady(file.toUri())
} catch (e: CancellationException) { } catch (e: CancellationException) {
//do nothing // do nothing
} catch (e: Exception) { } catch (e: Exception) {
state = State.ERROR state = State.ERROR
callback.onError(e) callback.onError(e)
@ -99,7 +104,7 @@ class PageHolderDelegate(
fun onImageReady(uri: Uri) fun onImageReady(uri: Uri)
fun onImageShowing() fun onImageShowing(zoom: ZoomMode)
fun onImageShown() fun onImageShown()
} }

@ -0,0 +1,48 @@
package org.koitharu.kotatsu.ui.reader.reversed
import android.graphics.PointF
import android.view.ViewGroup
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.android.synthetic.main.item_page.*
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.standard.PageHolder
class ReversedPageHolder(parent: ViewGroup, loader: PageLoader) : PageHolder(parent, loader) {
override fun onImageShowing(zoom: ZoomMode) {
ssiv.maxScale = 2f * maxOf(
ssiv.width / ssiv.sWidth.toFloat(),
ssiv.height / ssiv.sHeight.toFloat()
)
when (zoom) {
ZoomMode.FIT_CENTER -> {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
ssiv.resetScaleAndCenter()
}
ZoomMode.FIT_HEIGHT -> {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
ssiv.minScale = ssiv.height / ssiv.sHeight.toFloat()
ssiv.setScaleAndCenter(
ssiv.minScale,
PointF(ssiv.sWidth.toFloat(), ssiv.sHeight / 2f)
)
}
ZoomMode.FIT_WIDTH -> {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat()
ssiv.setScaleAndCenter(
ssiv.minScale,
PointF(ssiv.sWidth / 2f, 0f)
)
}
ZoomMode.KEEP_START -> {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
ssiv.setScaleAndCenter(
ssiv.maxScale,
PointF(ssiv.sWidth.toFloat(), 0f)
)
}
}
}
}

@ -5,14 +5,13 @@ import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
import org.koitharu.kotatsu.ui.reader.PageLoader import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
import org.koitharu.kotatsu.ui.reader.base.ReaderPage import org.koitharu.kotatsu.ui.reader.base.ReaderPage
import org.koitharu.kotatsu.ui.reader.standard.PageHolder
class ReversedPagesAdapter( class ReversedPagesAdapter(
pages: List<ReaderPage>, pages: List<ReaderPage>,
private val loader: PageLoader private val loader: PageLoader
) : BaseReaderAdapter(pages) { ) : BaseReaderAdapter(pages) {
override fun onCreateViewHolder(parent: ViewGroup) = PageHolder(parent, loader) override fun onCreateViewHolder(parent: ViewGroup) = ReversedPageHolder(parent, loader)
override fun onBindViewHolder(holder: BaseViewHolder<ReaderPage, Unit>, position: Int) { override fun onBindViewHolder(holder: BaseViewHolder<ReaderPage, Unit>, position: Int) {
super.onBindViewHolder(holder, reversed(position)) super.onBindViewHolder(holder, reversed(position))

@ -15,6 +15,7 @@ import org.koitharu.kotatsu.ui.reader.base.ReaderPage
import org.koitharu.kotatsu.ui.reader.standard.PageAnimTransformer import org.koitharu.kotatsu.ui.reader.standard.PageAnimTransformer
import org.koitharu.kotatsu.ui.reader.standard.PagerPaginationListener import org.koitharu.kotatsu.ui.reader.standard.PagerPaginationListener
import org.koitharu.kotatsu.utils.ext.doOnPageChanged import org.koitharu.kotatsu.utils.ext.doOnPageChanged
import org.koitharu.kotatsu.utils.ext.swapAdapter
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class ReversedReaderFragment : AbstractReader(R.layout.fragment_reader_standard), class ReversedReaderFragment : AbstractReader(R.layout.fragment_reader_standard),
@ -56,6 +57,11 @@ class ReversedReaderFragment : AbstractReader(R.layout.fragment_reader_standard)
return ReversedPagesAdapter(dataSet, loader) return ReversedPagesAdapter(dataSet, loader)
} }
override fun recreateAdapter() {
super.recreateAdapter()
pager.swapAdapter(adapter)
}
override fun getCurrentItem() = reversed(pager.currentItem) override fun getCurrentItem() = reversed(pager.currentItem)
override fun setCurrentItem(position: Int, isSmooth: Boolean) { override fun setCurrentItem(position: Int, isSmooth: Boolean) {

@ -1,19 +1,22 @@
package org.koitharu.kotatsu.ui.reader.standard package org.koitharu.kotatsu.ui.reader.standard
import android.graphics.PointF
import android.net.Uri import android.net.Uri
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.android.synthetic.main.item_page.* import kotlinx.android.synthetic.main.item_page.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.ui.base.list.BaseViewHolder import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
import org.koitharu.kotatsu.ui.reader.PageLoader import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.base.PageHolderDelegate import org.koitharu.kotatsu.ui.reader.base.PageHolderDelegate
import org.koitharu.kotatsu.ui.reader.base.ReaderPage import org.koitharu.kotatsu.ui.reader.base.ReaderPage
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class PageHolder(parent: ViewGroup, loader: PageLoader) : open class PageHolder(parent: ViewGroup, loader: PageLoader) :
BaseViewHolder<ReaderPage, Unit>(parent, R.layout.item_page), BaseViewHolder<ReaderPage, Unit>(parent, R.layout.item_page),
PageHolderDelegate.Callback, View.OnClickListener { PageHolderDelegate.Callback, View.OnClickListener {
@ -43,12 +46,40 @@ class PageHolder(parent: ViewGroup, loader: PageLoader) :
ssiv.setImage(ImageSource.uri(uri)) ssiv.setImage(ImageSource.uri(uri))
} }
override fun onImageShowing() { override fun onImageShowing(zoom: ZoomMode) {
ssiv.maxScale = 2f * maxOf( ssiv.maxScale = 2f * maxOf(
ssiv.width / ssiv.sWidth.toFloat(), ssiv.width / ssiv.sWidth.toFloat(),
ssiv.height / ssiv.sHeight.toFloat() ssiv.height / ssiv.sHeight.toFloat()
) )
ssiv.resetScaleAndCenter() when (zoom) {
ZoomMode.FIT_CENTER -> {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
ssiv.resetScaleAndCenter()
}
ZoomMode.FIT_HEIGHT -> {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
ssiv.minScale = ssiv.height / ssiv.sHeight.toFloat()
ssiv.setScaleAndCenter(
ssiv.minScale,
PointF(0f, ssiv.sHeight / 2f)
)
}
ZoomMode.FIT_WIDTH -> {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat()
ssiv.setScaleAndCenter(
ssiv.minScale,
PointF(ssiv.sWidth / 2f, 0f)
)
}
ZoomMode.KEEP_START -> {
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
ssiv.setScaleAndCenter(
ssiv.maxScale,
PointF(0f, 0f)
)
}
}
} }
override fun onImageShown() { override fun onImageShown() {

@ -13,6 +13,7 @@ import org.koitharu.kotatsu.ui.reader.base.AbstractReader
import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter import org.koitharu.kotatsu.ui.reader.base.BaseReaderAdapter
import org.koitharu.kotatsu.ui.reader.base.ReaderPage import org.koitharu.kotatsu.ui.reader.base.ReaderPage
import org.koitharu.kotatsu.utils.ext.doOnPageChanged import org.koitharu.kotatsu.utils.ext.doOnPageChanged
import org.koitharu.kotatsu.utils.ext.swapAdapter
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
class PagerReaderFragment : AbstractReader(R.layout.fragment_reader_standard), class PagerReaderFragment : AbstractReader(R.layout.fragment_reader_standard),
@ -52,6 +53,11 @@ class PagerReaderFragment : AbstractReader(R.layout.fragment_reader_standard),
return PagesAdapter(dataSet, loader) return PagesAdapter(dataSet, loader)
} }
override fun recreateAdapter() {
super.recreateAdapter()
pager.swapAdapter(adapter)
}
override fun getCurrentItem() = pager.currentItem override fun getCurrentItem() = pager.currentItem
override fun setCurrentItem(position: Int, isSmooth: Boolean) { override fun setCurrentItem(position: Int, isSmooth: Boolean) {

@ -8,6 +8,7 @@ import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import kotlinx.android.synthetic.main.item_page_webtoon.* import kotlinx.android.synthetic.main.item_page_webtoon.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.ui.base.list.BaseViewHolder import org.koitharu.kotatsu.ui.base.list.BaseViewHolder
import org.koitharu.kotatsu.ui.reader.PageLoader import org.koitharu.kotatsu.ui.reader.PageLoader
import org.koitharu.kotatsu.ui.reader.base.PageHolderDelegate import org.koitharu.kotatsu.ui.reader.base.PageHolderDelegate
@ -46,7 +47,7 @@ class WebtoonHolder(parent: ViewGroup, private val loader: PageLoader) :
ssiv.setImage(ImageSource.uri(uri)) ssiv.setImage(ImageSource.uri(uri))
} }
override fun onImageShowing() { override fun onImageShowing(zoom: ZoomMode) {
ssiv.maxScale = 2f * ssiv.width / ssiv.sWidth.toFloat() ssiv.maxScale = 2f * ssiv.width / ssiv.sWidth.toFloat()
ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM) ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat() ssiv.minScale = ssiv.width / ssiv.sWidth.toFloat()

@ -32,6 +32,11 @@ class WebtoonReaderFragment : AbstractReader(R.layout.fragment_reader_webtoon) {
return WebtoonAdapter(dataSet, loader) return WebtoonAdapter(dataSet, loader)
} }
override fun recreateAdapter() {
super.recreateAdapter()
recyclerView.swapAdapter(adapter, true)
}
override fun onDestroyView() { override fun onDestroyView() {
paginationListener = null paginationListener = null
super.onDestroyView() super.onDestroyView()

@ -16,6 +16,7 @@ import kotlinx.coroutines.launch
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.ui.base.BasePreferenceFragment import org.koitharu.kotatsu.ui.base.BasePreferenceFragment
@ -26,6 +27,7 @@ import org.koitharu.kotatsu.ui.settings.utils.MultiSummaryProvider
import org.koitharu.kotatsu.ui.tracker.TrackWorker import org.koitharu.kotatsu.ui.tracker.TrackWorker
import org.koitharu.kotatsu.utils.ext.getStorageName import org.koitharu.kotatsu.utils.ext.getStorageName
import org.koitharu.kotatsu.utils.ext.md5 import org.koitharu.kotatsu.utils.ext.md5
import org.koitharu.kotatsu.utils.ext.names
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
import java.io.File import java.io.File
@ -60,6 +62,11 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
summary = settings.getStorageDir(context)?.getStorageName(context) summary = settings.getStorageDir(context)?.getStorageName(context)
?: getString(R.string.not_available) ?: getString(R.string.not_available)
} }
findPreference<ListPreference>(AppSettings.KEY_ZOOM_MODE)?.let {
it.entryValues = ZoomMode.values().names()
it.setDefaultValue(ZoomMode.FIT_CENTER.name)
it.summaryProvider = ListPreference.SimpleSummaryProvider.getInstance()
}
findPreference<SwitchPreference>(AppSettings.KEY_PROTECT_APP)?.isChecked = findPreference<SwitchPreference>(AppSettings.KEY_PROTECT_APP)?.isChecked =
!settings.appPassword.isNullOrEmpty() !settings.appPassword.isNullOrEmpty()
findPreference<Preference>(AppSettings.KEY_APP_VERSION)?.run { findPreference<Preference>(AppSettings.KEY_APP_VERSION)?.run {

@ -2,11 +2,14 @@ package org.koitharu.kotatsu.ui.settings
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference import androidx.preference.MultiSelectListPreference
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.ui.base.BasePreferenceFragment import org.koitharu.kotatsu.ui.base.BasePreferenceFragment
import org.koitharu.kotatsu.ui.settings.utils.MultiSummaryProvider import org.koitharu.kotatsu.ui.settings.utils.MultiSummaryProvider
import org.koitharu.kotatsu.utils.ext.names
class ReaderSettingsFragment : BasePreferenceFragment(R.string.reader_settings) { class ReaderSettingsFragment : BasePreferenceFragment(R.string.reader_settings) {
@ -19,5 +22,10 @@ class ReaderSettingsFragment : BasePreferenceFragment(R.string.reader_settings)
findPreference<MultiSelectListPreference>(AppSettings.KEY_READER_SWITCHERS)?.let { findPreference<MultiSelectListPreference>(AppSettings.KEY_READER_SWITCHERS)?.let {
it.summaryProvider = MultiSummaryProvider(R.string.gestures_only) it.summaryProvider = MultiSummaryProvider(R.string.gestures_only)
} }
findPreference<ListPreference>(AppSettings.KEY_ZOOM_MODE)?.let {
it.entryValues = ZoomMode.values().names()
it.setDefaultValue(ZoomMode.FIT_CENTER.name)
it.summaryProvider = ListPreference.SimpleSummaryProvider.getInstance()
}
} }
} }

@ -9,20 +9,19 @@ class EnumPreferenceDelegate<E : Enum<*>>(
private val cls: Class<E>, private val cls: Class<E>,
private val key: String, private val key: String,
private val defValue: E private val defValue: E
) : ) : ReadWriteProperty<SharedPreferences, E> {
ReadWriteProperty<SharedPreferences, E> {
override fun getValue(thisRef: SharedPreferences, property: KProperty<*>): E { override fun getValue(thisRef: SharedPreferences, property: KProperty<*>): E {
val ord = thisRef.getInt(key, -1) val name = thisRef.getString(key, null)
if (ord == -1) { if (name === null) {
return defValue return defValue
} }
return cls.enumConstants?.firstOrNull { it.ordinal == ord } ?: defValue return cls.enumConstants?.find { it.name == name } ?: defValue
} }
override fun setValue(thisRef: SharedPreferences, property: KProperty<*>, value: E) { override fun setValue(thisRef: SharedPreferences, property: KProperty<*>, value: E) {
thisRef.edit { thisRef.edit {
putInt(key, value.ordinal) putString(key, value.name)
} }
} }
} }

@ -0,0 +1,28 @@
package org.koitharu.kotatsu.utils.delegates.prefs
import android.content.SharedPreferences
import androidx.core.content.edit
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
@Deprecated("")
class IntEnumPreferenceDelegate<E : Enum<*>>(
private val cls: Class<E>,
private val key: String,
private val defValue: E
) : ReadWriteProperty<SharedPreferences, E> {
override fun getValue(thisRef: SharedPreferences, property: KProperty<*>): E {
val ord = thisRef.getInt(key, -1)
if (ord == -1) {
return defValue
}
return cls.enumConstants?.getOrNull(ord) ?: defValue
}
override fun setValue(thisRef: SharedPreferences, property: KProperty<*>, value: E) {
thisRef.edit {
putInt(key, value.ordinal)
}
}
}

@ -66,4 +66,10 @@ inline fun <T> Collection<T>.associateByLong(selector: (T) -> Long): LongSparseA
result.put(selector(item), item) result.put(selector(item), item)
} }
return result return result
} }
inline fun <T, reified R> Array<T>.mapToArray(transform: (T) -> R): Array<R> = Array(size) { i ->
transform(get(i))
}
fun <T : Enum<T>> Array<T>.names() = mapToArray { it.name }

@ -200,4 +200,12 @@ fun RecyclerView.findCenterViewPosition(): Int {
val centerY = height / 2f val centerY = height / 2f
val view = findChildViewUnder(centerX, centerY) ?: return RecyclerView.NO_POSITION val view = findChildViewUnder(centerX, centerY) ?: return RecyclerView.NO_POSITION
return getChildAdapterPosition(view) return getChildAdapterPosition(view)
}
fun ViewPager2.swapAdapter(newAdapter: RecyclerView.Adapter<*>?) {
val position = currentItem
adapter = newAdapter
if (adapter != null && position != RecyclerView.NO_POSITION) {
currentItem = position
}
} }

@ -163,4 +163,9 @@
<string name="prefer_rtl_reader">Предпочитать режим Справа налево</string> <string name="prefer_rtl_reader">Предпочитать режим Справа налево</string>
<string name="prefer_rtl_reader_summary">Вы можете настроить режим чтения для каждой манги отдельно</string> <string name="prefer_rtl_reader_summary">Вы можете настроить режим чтения для каждой манги отдельно</string>
<string name="create_category">Создать категорию</string> <string name="create_category">Создать категорию</string>
<string name="scale_mode">Масштабирование</string>
<string name="zoom_mode_fit_center">Вписать в экран</string>
<string name="zoom_mode_fit_height">Подогнать по высоте</string>
<string name="zoom_mode_fit_width">Подогнать по ширине</string>
<string name="zoom_mode_keep_start">Исходный размер</string>
</resources> </resources>

@ -9,6 +9,12 @@
<item>@string/taps_on_edges</item> <item>@string/taps_on_edges</item>
<item>@string/volume_buttons</item> <item>@string/volume_buttons</item>
</string-array> </string-array>
<string-array name="zoom_modes">
<item>@string/zoom_mode_fit_center</item>
<item>@string/zoom_mode_fit_height</item>
<item>@string/zoom_mode_fit_width</item>
<item>@string/zoom_mode_keep_start</item>
</string-array>
<string-array name="track_sources"> <string-array name="track_sources">
<item>@string/favourites</item> <item>@string/favourites</item>
<item>@string/history</item> <item>@string/history</item>

@ -165,4 +165,9 @@
<string name="prefer_rtl_reader_summary">You can set up the reading mode for each manga separately</string> <string name="prefer_rtl_reader_summary">You can set up the reading mode for each manga separately</string>
<string name="create_category">New category</string> <string name="create_category">New category</string>
<string name="report_github">Create issue on GitHub</string> <string name="report_github">Create issue on GitHub</string>
<string name="scale_mode">Scale mode</string>
<string name="zoom_mode_fit_center">Fit center</string>
<string name="zoom_mode_fit_height">Fit to height</string>
<string name="zoom_mode_fit_width">Fit to width</string>
<string name="zoom_mode_keep_start">Keep at start</string>
</resources> </resources>

@ -70,6 +70,12 @@
android:title="@string/pages_animation" android:title="@string/pages_animation"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<ListPreference
android:entries="@array/zoom_modes"
android:key="zoom_mode"
android:title="@string/scale_mode"
app:iconSpaceReserved="false" />
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="reader_prefer_rtl" android:key="reader_prefer_rtl"

@ -17,6 +17,12 @@
android:title="@string/pages_animation" android:title="@string/pages_animation"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<ListPreference
android:entries="@array/zoom_modes"
android:key="zoom_mode"
android:title="@string/scale_mode"
app:iconSpaceReserved="false" />
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="reader_prefer_rtl" android:key="reader_prefer_rtl"

Loading…
Cancel
Save