Misc fixes

pull/571/head
Koitharu 2 years ago
parent 880dd6da27
commit acba312e8d
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -17,7 +17,7 @@ android {
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 599 versionCode = 599
versionName = '6.3.2' versionName = '6.4-a1'
generatedDensities = [] generatedDensities = []
testInstrumentationRunner "org.koitharu.kotatsu.HiltTestRunner" testInstrumentationRunner "org.koitharu.kotatsu.HiltTestRunner"
ksp { ksp {
@ -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:826d7b4512' implementation 'com.github.KotatsuApp:subsampling-scale-image-view:c7dab3aefe'
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'

@ -16,4 +16,8 @@ class PagerLifecycleDispatcher(
(wh as? LifecycleAwareViewHolder)?.setIsCurrent(wh.absoluteAdapterPosition == position) (wh as? LifecycleAwareViewHolder)?.setIsCurrent(wh.absoluteAdapterPosition == position)
} }
} }
fun invalidate() {
onPageSelected(pager.currentItem)
}
} }

@ -12,6 +12,10 @@ class RecyclerViewLifecycleDispatcher : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
invalidate(recyclerView)
}
fun invalidate(recyclerView: RecyclerView) {
val lm = recyclerView.layoutManager as? LinearLayoutManager ?: return val lm = recyclerView.layoutManager as? LinearLayoutManager ?: return
val first = lm.findFirstVisibleItemPosition() val first = lm.findFirstVisibleItemPosition()
val last = lm.findLastVisibleItemPosition() val last = lm.findLastVisibleItemPosition()

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.local.data package org.koitharu.kotatsu.local.data
import android.net.Uri import android.net.Uri
import org.koitharu.kotatsu.core.util.ext.URI_SCHEME_ZIP
import java.io.File import java.io.File
private fun isCbzExtension(ext: String?): Boolean { private fun isCbzExtension(ext: String?): Boolean {
@ -12,6 +13,8 @@ fun hasCbzExtension(string: String): Boolean {
return isCbzExtension(ext) return isCbzExtension(ext)
} }
fun hasCbzExtension(file: File) = isCbzExtension(file.extension) fun File.hasCbzExtension() = isCbzExtension(extension)
fun isCbzUri(uri: Uri) = isCbzExtension(uri.scheme) fun Uri.isZipUri() = scheme.let {
it == URI_SCHEME_ZIP || it == "cbz" || it == "zip"
}

@ -121,13 +121,13 @@ class LocalMangaDirInput(root: File) : LocalMangaInput(root) {
private fun String.toHumanReadable() = replace("_", " ").toCamelCase() private fun String.toHumanReadable() = replace("_", " ").toCamelCase()
private fun getChaptersFiles(): List<File> = root.walkCompat() private fun getChaptersFiles(): List<File> = root.walkCompat()
.filter { hasCbzExtension(it) } .filter { it.hasCbzExtension() }
.toListSorted(compareBy(AlphanumComparator()) { it.name }) .toListSorted(compareBy(AlphanumComparator()) { it.name })
private fun findFirstImageEntry(): String? { private fun findFirstImageEntry(): String? {
return root.walkCompat().firstOrNull { hasImageExtension(it) }?.toUri()?.toString() return root.walkCompat().firstOrNull { hasImageExtension(it) }?.toUri()?.toString()
?: run { ?: run {
val cbz = root.walkCompat().firstOrNull { hasCbzExtension(it) } ?: return null val cbz = root.walkCompat().firstOrNull { it.hasCbzExtension() } ?: return null
ZipFile(cbz).use { zip -> ZipFile(cbz).use { zip ->
zip.entries().asSequence() zip.entries().asSequence()
.firstOrNull { !it.isDirectory && hasImageExtension(it.name) } .firstOrNull { !it.isDirectory && hasImageExtension(it.name) }

@ -45,7 +45,7 @@ import org.koitharu.kotatsu.core.util.ext.ramAvailable
import org.koitharu.kotatsu.core.util.ext.withProgress import org.koitharu.kotatsu.core.util.ext.withProgress
import org.koitharu.kotatsu.core.util.progress.ProgressDeferred import org.koitharu.kotatsu.core.util.progress.ProgressDeferred
import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.local.data.isCbzUri import org.koitharu.kotatsu.local.data.isZipUri
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
@ -199,8 +199,12 @@ class PageLoader @Inject constructor(
val pageUrl = getPageUrl(page) val pageUrl = getPageUrl(page)
check(pageUrl.isNotBlank()) { "Cannot obtain full image url" } check(pageUrl.isNotBlank()) { "Cannot obtain full image url" }
val uri = Uri.parse(pageUrl) val uri = Uri.parse(pageUrl)
return if (isCbzUri(uri)) { return if (uri.isZipUri()) {
uri.buildUpon().scheme(URI_SCHEME_ZIP).build() if (uri.scheme == URI_SCHEME_ZIP) {
uri
} else { // legacy uri
uri.buildUpon().scheme(URI_SCHEME_ZIP).build()
}
} else { } else {
val request = createPageRequest(page, pageUrl) val request = createPageRequest(page, pageUrl)
imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response -> imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response ->

@ -11,12 +11,13 @@ import android.view.ViewGroup
import androidx.core.view.children import androidx.core.view.children
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
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.prefs.ReaderAnimation import org.koitharu.kotatsu.core.prefs.ReaderAnimation
import org.koitharu.kotatsu.core.ui.list.lifecycle.PagerLifecycleDispatcher
import org.koitharu.kotatsu.core.util.ext.doOnPageChanged import org.koitharu.kotatsu.core.util.ext.doOnPageChanged
import org.koitharu.kotatsu.core.util.ext.findCurrentViewHolder import org.koitharu.kotatsu.core.util.ext.findCurrentViewHolder
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
@ -46,6 +47,8 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
@Inject @Inject
lateinit var pageLoader: PageLoader lateinit var pageLoader: PageLoader
private var pagerLifecycleDispatcher: PagerLifecycleDispatcher? = null
override fun onCreateViewBinding( override fun onCreateViewBinding(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -62,6 +65,9 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
recyclerView?.defaultFocusHighlightEnabled = false recyclerView?.defaultFocusHighlightEnabled = false
} }
PagerEventSupplier(this).attach() PagerEventSupplier(this).attach()
pagerLifecycleDispatcher = PagerLifecycleDispatcher(this).also {
registerOnPageChangeCallback(it)
}
} }
viewModel.pageAnimation.observe(viewLifecycleOwner) { viewModel.pageAnimation.observe(viewLifecycleOwner) {
@ -80,6 +86,7 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
} }
override fun onDestroyView() { override fun onDestroyView() {
pagerLifecycleDispatcher = null
requireViewBinding().pager.adapter = null requireViewBinding().pager.adapter = null
super.onDestroyView() super.onDestroyView()
} }
@ -132,15 +139,16 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope { override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
val reversedPages = pages.asReversed() val reversedPages = pages.asReversed()
val items = async { val items = launch {
requireAdapter().setItems(reversedPages) requireAdapter().setItems(reversedPages)
yield() yield()
pagerLifecycleDispatcher?.invalidate()
} }
if (pendingState != null) { if (pendingState != null) {
val position = reversedPages.indexOfLast { val position = reversedPages.indexOfLast {
it.chapterId == pendingState.chapterId && it.index == pendingState.page it.chapterId == pendingState.chapterId && it.index == pendingState.page
} }
items.await() items.join()
if (position != -1) { if (position != -1) {
requireViewBinding().pager.setCurrentItem(position, false) requireViewBinding().pager.setCurrentItem(position, false)
notifyPageChanged(position) notifyPageChanged(position)
@ -149,7 +157,7 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
.show() .show()
} }
} else { } else {
items.await() items.join()
} }
} }

@ -11,8 +11,8 @@ import android.view.ViewGroup
import androidx.core.view.children import androidx.core.view.children
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
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
@ -43,6 +43,8 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>(),
@Inject @Inject
lateinit var pageLoader: PageLoader lateinit var pageLoader: PageLoader
private var pagerLifecycleDispatcher: PagerLifecycleDispatcher? = null
override fun onCreateViewBinding( override fun onCreateViewBinding(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -62,7 +64,9 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>(),
recyclerView?.defaultFocusHighlightEnabled = false recyclerView?.defaultFocusHighlightEnabled = false
} }
PagerEventSupplier(this).attach() PagerEventSupplier(this).attach()
registerOnPageChangeCallback(PagerLifecycleDispatcher(this)) pagerLifecycleDispatcher = PagerLifecycleDispatcher(this).also {
registerOnPageChangeCallback(it)
}
} }
viewModel.pageAnimation.observe(viewLifecycleOwner) { viewModel.pageAnimation.observe(viewLifecycleOwner) {
@ -81,6 +85,7 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>(),
} }
override fun onDestroyView() { override fun onDestroyView() {
pagerLifecycleDispatcher = null
requireViewBinding().pager.adapter = null requireViewBinding().pager.adapter = null
super.onDestroyView() super.onDestroyView()
} }
@ -109,15 +114,16 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>(),
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) =
coroutineScope { coroutineScope {
val items = async { val items = launch {
requireAdapter().setItems(pages) requireAdapter().setItems(pages)
yield() yield()
pagerLifecycleDispatcher?.invalidate()
} }
if (pendingState != null) { if (pendingState != null) {
val position = pages.indexOfFirst { val position = pages.indexOfFirst {
it.chapterId == pendingState.chapterId && it.index == pendingState.page it.chapterId == pendingState.chapterId && it.index == pendingState.page
} }
items.await() items.join()
if (position != -1) { if (position != -1) {
requireViewBinding().pager.setCurrentItem(position, false) requireViewBinding().pager.setCurrentItem(position, false)
notifyPageChanged(position) notifyPageChanged(position)
@ -126,7 +132,7 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>(),
.show() .show()
} }
} else { } else {
items.await() items.join()
} }
} }

@ -6,8 +6,8 @@ import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
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
@ -34,6 +34,8 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
private val scrollInterpolator = DecelerateInterpolator() private val scrollInterpolator = DecelerateInterpolator()
private var recyclerLifecycleDispatcher: RecyclerViewLifecycleDispatcher? = null
override fun onCreateViewBinding( override fun onCreateViewBinding(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -45,7 +47,9 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
setHasFixedSize(true) setHasFixedSize(true)
adapter = readerAdapter adapter = readerAdapter
addOnPageScrollListener(PageScrollListener()) addOnPageScrollListener(PageScrollListener())
addOnScrollListener(RecyclerViewLifecycleDispatcher()) recyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also {
addOnScrollListener(it)
}
} }
viewModel.isWebtoonZooEnabled.observe(viewLifecycleOwner) { viewModel.isWebtoonZooEnabled.observe(viewLifecycleOwner) {
binding.frame.isZoomEnable = it binding.frame.isZoomEnable = it
@ -53,6 +57,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
} }
override fun onDestroyView() { override fun onDestroyView() {
recyclerLifecycleDispatcher = null
requireViewBinding().recyclerView.adapter = null requireViewBinding().recyclerView.adapter = null
super.onDestroyView() super.onDestroyView()
} }
@ -66,15 +71,18 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
) )
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope { override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
val setItems = async { val setItems = launch {
requireAdapter().setItems(pages) requireAdapter().setItems(pages)
yield() yield()
viewBinding?.recyclerView?.let { rv ->
recyclerLifecycleDispatcher?.invalidate(rv)
}
} }
if (pendingState != null) { if (pendingState != null) {
val position = pages.indexOfFirst { val position = pages.indexOfFirst {
it.chapterId == pendingState.chapterId && it.index == pendingState.page it.chapterId == pendingState.chapterId && it.index == pendingState.page
} }
setItems.await() setItems.join()
if (position != -1) { if (position != -1) {
with(requireViewBinding().recyclerView) { with(requireViewBinding().recyclerView) {
firstVisibleItemPosition = position firstVisibleItemPosition = position
@ -89,7 +97,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
.show() .show()
} }
} else { } else {
setItems.await() setItems.join()
} }
} }

@ -20,7 +20,7 @@ import org.koitharu.kotatsu.core.network.ImageProxyInterceptor
import org.koitharu.kotatsu.core.network.MangaHttpClient import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.local.data.isCbzUri import org.koitharu.kotatsu.local.data.isZipUri
import org.koitharu.kotatsu.local.data.util.withExtraCloseable import org.koitharu.kotatsu.local.data.util.withExtraCloseable
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.mimeType import org.koitharu.kotatsu.parsers.util.mimeType
@ -56,7 +56,7 @@ class MangaPageFetcher(
private suspend fun loadPage(pageUrl: String): SourceResult { private suspend fun loadPage(pageUrl: String): SourceResult {
val uri = pageUrl.toUri() val uri = pageUrl.toUri()
return if (isCbzUri(uri)) { return if (uri.isZipUri()) {
runInterruptible(Dispatchers.IO) { runInterruptible(Dispatchers.IO) {
val zip = ZipFile(uri.schemeSpecificPart) val zip = ZipFile(uri.schemeSpecificPart)
val entry = zip.getEntry(uri.fragment) val entry = zip.getEntry(uri.fragment)

Loading…
Cancel
Save