Reader refactoring

This commit is contained in:
Koitharu
2023-06-12 12:47:05 +03:00
parent 5e55bce529
commit a3cf52859b
19 changed files with 203 additions and 170 deletions

View File

@@ -398,6 +398,44 @@ class ReaderActivity :
}
}
class IntentBuilder(context: Context) {
private val intent = Intent(context, ReaderActivity::class.java)
.setAction(ACTION_MANGA_READ)
fun manga(manga: Manga) = apply {
intent.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = true))
}
fun mangaId(mangaId: Long) = apply {
intent.putExtra(MangaIntent.KEY_ID, mangaId)
}
fun incognito(incognito: Boolean) = apply {
intent.putExtra(EXTRA_INCOGNITO, incognito)
}
fun branch(branch: String?) = apply {
intent.putExtra(EXTRA_BRANCH, branch)
}
fun state(state: ReaderState?) = apply {
intent.putExtra(EXTRA_STATE, state)
}
fun bookmark(bookmark: Bookmark) = manga(
bookmark.manga,
).state(
ReaderState(
chapterId = bookmark.chapterId,
page = bookmark.page,
scroll = bookmark.scroll,
),
)
fun build() = intent
}
companion object {
const val ACTION_MANGA_READ = "${BuildConfig.APPLICATION_ID}.action.READ_MANGA"
@@ -405,38 +443,5 @@ class ReaderActivity :
const val EXTRA_BRANCH = "branch"
const val EXTRA_INCOGNITO = "incognito"
private const val TOAST_DURATION = 1500L
fun newIntent(context: Context, manga: Manga): Intent {
return Intent(context, ReaderActivity::class.java)
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = true))
}
fun newIntent(context: Context, manga: Manga, branch: String?, isIncognitoMode: Boolean): Intent {
return Intent(context, ReaderActivity::class.java)
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = true))
.putExtra(EXTRA_BRANCH, branch)
.putExtra(EXTRA_INCOGNITO, isIncognitoMode)
}
fun newIntent(context: Context, manga: Manga, state: ReaderState?): Intent {
return Intent(context, ReaderActivity::class.java)
.putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga, withChapters = true))
.putExtra(EXTRA_STATE, state)
}
fun newIntent(context: Context, bookmark: Bookmark): Intent {
val state = ReaderState(
chapterId = bookmark.chapterId,
page = bookmark.page,
scroll = bookmark.scroll,
)
return newIntent(context, bookmark.manga, state)
.putExtra(EXTRA_INCOGNITO, true)
}
fun newIntent(context: Context, mangaId: Long): Intent {
return Intent(context, ReaderActivity::class.java)
.putExtra(MangaIntent.KEY_ID, mangaId)
}
}
}

View File

@@ -312,8 +312,10 @@ class ReaderViewModel @Inject constructor(
private fun loadImpl() {
loadingJob = launchLoadingJob(Dispatchers.Default) {
var manga =
DoubleManga(dataRepository.resolveIntent(intent) ?: throw NotFoundException("Cannot find manga", ""))
var manga = DoubleManga(
dataRepository.resolveIntent(intent)
?: throw NotFoundException("Cannot find manga", ""),
)
mangaData.value = manga
manga = doubleMangaLoadUseCase(intent)
chaptersLoader.init(manga)

View File

@@ -17,9 +17,13 @@ abstract class BaseReaderFragment<B : ViewBinding> : BaseFragment<B>() {
protected val viewModel by activityViewModels<ReaderViewModel>()
private var stateToSave: ReaderState? = null
protected var readerAdapter: BaseReaderAdapter<*>? = null
private set
override fun onViewBindingCreated(binding: B, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
var restoredState = savedInstanceState?.getParcelableCompat<ReaderState>(KEY_STATE)
readerAdapter = onCreateAdapter()
viewModel.content.observe(viewLifecycleOwner) {
onPagesChanged(it.pages, restoredState ?: it.state)
@@ -34,6 +38,7 @@ abstract class BaseReaderFragment<B : ViewBinding> : BaseFragment<B>() {
override fun onDestroyView() {
stateToSave = getCurrentState()
readerAdapter = null
super.onDestroyView()
}
@@ -45,6 +50,10 @@ abstract class BaseReaderFragment<B : ViewBinding> : BaseFragment<B>() {
outState.putParcelable(KEY_STATE, stateToSave)
}
protected fun requireAdapter() = checkNotNull(readerAdapter) {
"Adapter was not created or already destroyed"
}
override fun onWindowInsetsChanged(insets: Insets) = Unit
abstract fun switchPageBy(delta: Int)
@@ -55,5 +64,7 @@ abstract class BaseReaderFragment<B : ViewBinding> : BaseFragment<B>() {
abstract fun getCurrentState(): ReaderState?
protected abstract fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?)
protected abstract fun onCreateAdapter(): BaseReaderAdapter<*>
protected abstract suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?)
}

View File

@@ -4,16 +4,18 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.children
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.yield
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.util.ext.doOnPageChanged
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.recyclerView
import org.koitharu.kotatsu.core.util.ext.resetTransformations
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.ReaderState
@@ -33,8 +35,6 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
@Inject
lateinit var pageLoader: PageLoader
private var pagerAdapter: ReversedPagesAdapter? = null
override fun onCreateViewBinding(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -42,15 +42,8 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
override fun onViewBindingCreated(binding: FragmentReaderStandardBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
pagerAdapter = ReversedPagesAdapter(
lifecycleOwner = viewLifecycleOwner,
loader = pageLoader,
settings = viewModel.readerSettings,
networkState = networkState,
exceptionResolver = exceptionResolver,
)
with(binding.pager) {
adapter = pagerAdapter
adapter = readerAdapter
offscreenPageLimit = 2
doOnPageChanged(::notifyPageChanged)
}
@@ -67,11 +60,18 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
}
override fun onDestroyView() {
pagerAdapter = null
requireViewBinding().pager.adapter = null
super.onDestroyView()
}
override fun onCreateAdapter() = ReversedPagesAdapter(
lifecycleOwner = viewLifecycleOwner,
loader = pageLoader,
settings = viewModel.readerSettings,
networkState = networkState,
exceptionResolver = exceptionResolver,
)
override fun switchPageBy(delta: Int) {
with(requireViewBinding().pager) {
setCurrentItem(currentItem - delta, context.isAnimationsEnabled)
@@ -87,24 +87,26 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
}
}
override fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) {
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
val reversedPages = pages.asReversed()
viewLifecycleScope.launch {
val items = async {
pagerAdapter?.setItems(reversedPages)
val items = async {
requireAdapter().setItems(reversedPages)
yield()
}
if (pendingState != null) {
val position = reversedPages.indexOfLast {
it.chapterId == pendingState.chapterId && it.index == pendingState.page
}
if (pendingState != null) {
val position = reversedPages.indexOfLast {
it.chapterId == pendingState.chapterId && it.index == pendingState.page
}
items.await() ?: return@launch
if (position != -1) {
requireViewBinding().pager.setCurrentItem(position, false)
notifyPageChanged(position)
}
items.await()
if (position != -1) {
requireViewBinding().pager.setCurrentItem(position, false)
notifyPageChanged(position)
} else {
items.await()
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
.show()
}
} else {
items.await()
}
}
@@ -123,6 +125,6 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
}
private fun reversed(position: Int): Int {
return ((pagerAdapter?.itemCount ?: 0) - position - 1).coerceAtLeast(0)
return ((readerAdapter?.itemCount ?: 0) - position - 1).coerceAtLeast(0)
}
}

View File

@@ -4,16 +4,18 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.children
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.yield
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.util.ext.doOnPageChanged
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.recyclerView
import org.koitharu.kotatsu.core.util.ext.resetTransformations
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.ReaderState
@@ -32,8 +34,6 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>()
@Inject
lateinit var pageLoader: PageLoader
private var pagesAdapter: PagesAdapter? = null
override fun onCreateViewBinding(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -41,15 +41,8 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>()
override fun onViewBindingCreated(binding: FragmentReaderStandardBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
pagesAdapter = PagesAdapter(
lifecycleOwner = viewLifecycleOwner,
loader = pageLoader,
settings = viewModel.readerSettings,
networkState = networkState,
exceptionResolver = exceptionResolver,
)
with(binding.pager) {
adapter = pagesAdapter
adapter = readerAdapter
offscreenPageLimit = 2
doOnPageChanged(::notifyPageChanged)
}
@@ -66,31 +59,40 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>()
}
override fun onDestroyView() {
pagesAdapter = null
requireViewBinding().pager.adapter = null
super.onDestroyView()
}
override fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) {
viewLifecycleScope.launch {
val items = async {
pagesAdapter?.setItems(pages)
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
val items = async {
requireAdapter().setItems(pages)
yield()
}
if (pendingState != null) {
val position = pages.indexOfFirst {
it.chapterId == pendingState.chapterId && it.index == pendingState.page
}
if (pendingState != null) {
val position = pages.indexOfFirst {
it.chapterId == pendingState.chapterId && it.index == pendingState.page
}
items.await() ?: return@launch
if (position != -1) {
requireViewBinding().pager.setCurrentItem(position, false)
notifyPageChanged(position)
}
items.await()
if (position != -1) {
requireViewBinding().pager.setCurrentItem(position, false)
notifyPageChanged(position)
} else {
items.await()
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
.show()
}
} else {
items.await()
}
}
override fun onCreateAdapter() = PagesAdapter(
lifecycleOwner = viewLifecycleOwner,
loader = pageLoader,
settings = viewModel.readerSettings,
networkState = networkState,
exceptionResolver = exceptionResolver,
)
override fun switchPageBy(delta: Int) {
with(requireViewBinding().pager) {
setCurrentItem(currentItem + delta, context.isAnimationsEnabled)

View File

@@ -4,15 +4,17 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.yield
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.databinding.FragmentReaderWebtoonBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.ReaderState
@@ -31,7 +33,6 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
lateinit var pageLoader: PageLoader
private val scrollInterpolator = AccelerateDecelerateInterpolator()
private var webtoonAdapter: WebtoonAdapter? = null
override fun onCreateViewBinding(
inflater: LayoutInflater,
@@ -40,16 +41,9 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
override fun onViewBindingCreated(binding: FragmentReaderWebtoonBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
webtoonAdapter = WebtoonAdapter(
lifecycleOwner = viewLifecycleOwner,
loader = pageLoader,
settings = viewModel.readerSettings,
networkState = networkState,
exceptionResolver = exceptionResolver,
)
with(binding.recyclerView) {
setHasFixedSize(true)
adapter = webtoonAdapter
adapter = readerAdapter
addOnPageScrollListener(PageScrollListener())
}
@@ -59,32 +53,43 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
}
override fun onDestroyView() {
webtoonAdapter = null
requireViewBinding().recyclerView.adapter = null
super.onDestroyView()
}
override fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) {
viewLifecycleScope.launch {
val setItems = async { webtoonAdapter?.setItems(pages) }
if (pendingState != null) {
val position = pages.indexOfFirst {
it.chapterId == pendingState.chapterId && it.index == pendingState.page
}
setItems.await() ?: return@launch
if (position != -1) {
with(requireViewBinding().recyclerView) {
firstVisibleItemPosition = position
post {
(findViewHolderForAdapterPosition(position) as? WebtoonHolder)
?.restoreScroll(pendingState.scroll)
}
}
notifyPageChanged(position)
}
} else {
setItems.await()
override fun onCreateAdapter() = WebtoonAdapter(
lifecycleOwner = viewLifecycleOwner,
loader = pageLoader,
settings = viewModel.readerSettings,
networkState = networkState,
exceptionResolver = exceptionResolver,
)
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
val setItems = async {
requireAdapter().setItems(pages)
yield()
}
if (pendingState != null) {
val position = pages.indexOfFirst {
it.chapterId == pendingState.chapterId && it.index == pendingState.page
}
setItems.await()
if (position != -1) {
with(requireViewBinding().recyclerView) {
firstVisibleItemPosition = position
post {
(findViewHolderForAdapterPosition(position) as? WebtoonHolder)
?.restoreScroll(pendingState.scroll)
}
}
notifyPageChanged(position)
} else {
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
.show()
}
} else {
setItems.await()
}
}

View File

@@ -30,7 +30,7 @@ import org.koitharu.kotatsu.databinding.SheetPagesBinding
import org.koitharu.kotatsu.list.ui.MangaListSpanResolver
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.thumbnails.adapter.PageThumbnailAdapter
import javax.inject.Inject
@@ -101,8 +101,8 @@ class PagesThumbnailsSheet :
listener.onPageSelected(item.page)
} else {
val state = ReaderState(item.page.chapterId, item.page.index, 0)
val intent = ReaderActivity.newIntent(view.context, viewModel.manga, state)
startActivity(intent, scaleUpActivityOptionsOf(view).toBundle())
val intent = IntentBuilder(view.context).manga(viewModel.manga).state(state).build()
startActivity(intent, scaleUpActivityOptionsOf(view))
}
dismiss()
}