Improve pages preview on details screen

pull/620/head
Koitharu 2 years ago
parent d6012f9ddd
commit 7247cba855
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -82,7 +82,7 @@ afterEvaluate {
} }
dependencies { dependencies {
//noinspection GradleDependency //noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:7c89f53988') { implementation('com.github.KotatsuApp:kotatsu-parsers:3feb84ac9e') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }

@ -204,6 +204,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val isUnstableUpdatesAllowed: Boolean val isUnstableUpdatesAllowed: Boolean
get() = prefs.getBoolean(KEY_UPDATES_UNSTABLE, false) get() = prefs.getBoolean(KEY_UPDATES_UNSTABLE, false)
val defaultDetailsTab: Int
get() = prefs.getString(KEY_DETAILS_TAB, null)?.toIntOrNull()?.coerceIn(0, 1) ?: 0
val isContentPrefetchEnabled: Boolean val isContentPrefetchEnabled: Boolean
get() { get() {
if (isBackgroundNetworkRestricted()) { if (isBackgroundNetworkRestricted()) {
@ -559,6 +562,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_CF_INVERTED = "cf_inverted" const val KEY_CF_INVERTED = "cf_inverted"
const val KEY_CF_GRAYSCALE = "cf_grayscale" const val KEY_CF_GRAYSCALE = "cf_grayscale"
const val KEY_IGNORE_DOZE = "ignore_dose" const val KEY_IGNORE_DOZE = "ignore_dose"
const val KEY_DETAILS_TAB = "details_tab"
// About // About
const val KEY_APP_UPDATE = "app_update" const val KEY_APP_UPDATE = "app_update"

@ -38,4 +38,12 @@ abstract class BoundsScrollListener(
firstVisibleItemPosition: Int, firstVisibleItemPosition: Int,
visibleItemCount: Int visibleItemCount: Int
) = Unit ) = Unit
fun invalidate(recyclerView: RecyclerView) {
onScrolled(recyclerView, 0, 0)
}
fun postInvalidate(recyclerView: RecyclerView) = recyclerView.post {
invalidate(recyclerView)
}
} }

@ -1,6 +1,5 @@
package org.koitharu.kotatsu.details.ui package org.koitharu.kotatsu.details.ui
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -57,9 +56,6 @@ class ChaptersFragment :
checkNotNull(selectionController).attachToRecyclerView(this) checkNotNull(selectionController).attachToRecyclerView(this)
setHasFixedSize(true) setHasFixedSize(true)
adapter = chaptersAdapter adapter = chaptersAdapter
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
scrollIndicators = if (resources.getBoolean(R.bool.is_tablet)) 0 else View.SCROLL_INDICATOR_TOP
}
} }
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged) viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)
viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged) viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged)

@ -27,7 +27,6 @@ import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -38,8 +37,10 @@ import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.parser.MangaIntent import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.doOnExpansionsChanged import org.koitharu.kotatsu.core.util.ext.doOnExpansionsChanged
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
import org.koitharu.kotatsu.core.util.ext.getThemeColor import org.koitharu.kotatsu.core.util.ext.getThemeColor
@ -77,6 +78,9 @@ class DetailsActivity :
@Inject @Inject
lateinit var appShortcutManager: AppShortcutManager lateinit var appShortcutManager: AppShortcutManager
@Inject
lateinit var settings: AppSettings
private var buttonTip: WeakReference<ButtonTip>? = null private var buttonTip: WeakReference<ButtonTip>? = null
private val viewModel: DetailsViewModel by viewModels() private val viewModel: DetailsViewModel by viewModels()
@ -129,19 +133,16 @@ class DetailsActivity :
}, },
), ),
) )
viewModel.onShowToast.observeEvent(this) { viewModel.onActionDone.observeEvent(this, ReversibleActionObserver(viewBinding.containerDetails))
makeSnackbar(getString(it), Snackbar.LENGTH_SHORT).show()
}
viewModel.onShowTip.observeEvent(this) { showTip() } viewModel.onShowTip.observeEvent(this) { showTip() }
viewModel.historyInfo.observe(this, ::onHistoryChanged) viewModel.historyInfo.observe(this, ::onHistoryChanged)
viewModel.selectedBranch.observe(this) { viewModel.selectedBranch.observe(this) {
viewBinding.toolbarChapters?.subtitle = it viewBinding.toolbarChapters?.subtitle = it
viewBinding.textViewSubtitle?.textAndVisible = it viewBinding.textViewSubtitle?.textAndVisible = it
} }
viewModel.isChaptersReversed.observe( val chaptersMenuInvalidator = MenuInvalidator(viewBinding.toolbarChapters ?: this)
this, viewModel.isChaptersReversed.observe(this, chaptersMenuInvalidator)
MenuInvalidator(viewBinding.toolbarChapters ?: this), viewModel.isChaptersEmpty.observe(this, chaptersMenuInvalidator)
)
val menuInvalidator = MenuInvalidator(this) val menuInvalidator = MenuInvalidator(this)
viewModel.favouriteCategories.observe(this, menuInvalidator) viewModel.favouriteCategories.observe(this, menuInvalidator)
viewModel.remoteManga.observe(this, menuInvalidator) viewModel.remoteManga.observe(this, menuInvalidator)
@ -304,7 +305,7 @@ class DetailsActivity :
tab.removeBadge() tab.removeBadge()
} else { } else {
val badge = tab.orCreateBadge val badge = tab.orCreateBadge
badge.horizontalOffset = resources.getDimensionPixelOffset(R.dimen.margin_small) badge.horizontalOffsetWithText = -resources.getDimensionPixelOffset(R.dimen.margin_small)
badge.number = count badge.number = count
badge.isVisible = true badge.isVisible = true
} }
@ -343,9 +344,8 @@ class DetailsActivity :
val manga = viewModel.manga.value ?: return val manga = viewModel.manga.value ?: return
val chapterId = viewModel.historyInfo.value.history?.chapterId val chapterId = viewModel.historyInfo.value.history?.chapterId
if (chapterId != null && manga.chapters?.none { x -> x.id == chapterId } == true) { if (chapterId != null && manga.chapters?.none { x -> x.id == chapterId } == true) {
val snackbar = Snackbar.make(viewBinding.containerDetails, R.string.chapter_is_missing, Snackbar.LENGTH_SHORT)
makeSnackbar(getString(R.string.chapter_is_missing), Snackbar.LENGTH_SHORT) .show()
snackbar.show()
} else { } else {
startActivity( startActivity(
IntentBuilder(this) IntentBuilder(this)
@ -365,6 +365,7 @@ class DetailsActivity :
val adapter = DetailsPagerAdapter(this) val adapter = DetailsPagerAdapter(this)
viewBinding.pager.adapter = adapter viewBinding.pager.adapter = adapter
TabLayoutMediator(viewBinding.tabs, viewBinding.pager, adapter).attach() TabLayoutMediator(viewBinding.tabs, viewBinding.pager, adapter).attach()
viewBinding.pager.setCurrentItem(settings.defaultDetailsTab, false)
} }
private fun showBottomSheet(isVisible: Boolean) { private fun showBottomSheet(isVisible: Boolean) {
@ -377,17 +378,6 @@ class DetailsActivity :
view.isVisible = isVisible view.isVisible = isVisible
} }
private fun makeSnackbar(
text: CharSequence,
@BaseTransientBottomBar.Duration duration: Int,
): Snackbar {
val sb = Snackbar.make(viewBinding.containerDetails, text, duration)
if (viewBinding.layoutBottom?.isVisible == true) {
sb.anchorView = viewBinding.toolbarChapters
}
return sb
}
private class PrefetchObserver( private class PrefetchObserver(
private val context: Context, private val context: Context,
) : FlowCollector<List<ChapterListItem>?> { ) : FlowCollector<List<ChapterListItem>?> {

@ -10,8 +10,6 @@ import android.widget.Toast
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.text.buildSpannedString
import androidx.core.text.color
import androidx.core.text.method.LinkMovementMethodCompat import androidx.core.text.method.LinkMovementMethodCompat
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -23,7 +21,6 @@ import coil.request.SuccessResult
import coil.util.CoilUtils import coil.util.CoilUtils
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.bookmarks.domain.Bookmark
@ -42,7 +39,6 @@ import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.ext.crossfade import org.koitharu.kotatsu.core.util.ext.crossfade
import org.koitharu.kotatsu.core.util.ext.drawableTop import org.koitharu.kotatsu.core.util.ext.drawableTop
import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
import org.koitharu.kotatsu.core.util.ext.isTextTruncated import org.koitharu.kotatsu.core.util.ext.isTextTruncated
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
@ -75,7 +71,6 @@ import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorShee
import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.search.ui.SearchActivity
import javax.inject.Inject import javax.inject.Inject
import com.google.android.material.R as materialR
@AndroidEntryPoint @AndroidEntryPoint
class DetailsFragment : class DetailsFragment :
@ -122,7 +117,7 @@ class DetailsFragment :
viewModel.description.observe(viewLifecycleOwner, ::onDescriptionChanged) viewModel.description.observe(viewLifecycleOwner, ::onDescriptionChanged)
viewModel.localSize.observe(viewLifecycleOwner, ::onLocalSizeChanged) viewModel.localSize.observe(viewLifecycleOwner, ::onLocalSizeChanged)
viewModel.relatedManga.observe(viewLifecycleOwner, ::onRelatedMangaChanged) viewModel.relatedManga.observe(viewLifecycleOwner, ::onRelatedMangaChanged)
combine(viewModel.chapters, viewModel.newChaptersCount, ::Pair).observe(viewLifecycleOwner, ::onChaptersChanged) viewModel.chapters.observe(viewLifecycleOwner, ::onChaptersChanged)
} }
override fun onItemClick(item: Bookmark, view: View) { override fun onItemClick(item: Bookmark, view: View) {
@ -204,8 +199,7 @@ class DetailsFragment :
} }
} }
private fun onChaptersChanged(data: Pair<List<ChapterListItem>?, Int>) { private fun onChaptersChanged(chapters: List<ChapterListItem>?) {
val (chapters, newChapters) = data
val infoLayout = requireViewBinding().infoLayout val infoLayout = requireViewBinding().infoLayout
if (chapters.isNullOrEmpty()) { if (chapters.isNullOrEmpty()) {
infoLayout.textViewChapters.isVisible = false infoLayout.textViewChapters.isVisible = false
@ -213,19 +207,7 @@ class DetailsFragment :
val count = chapters.countChaptersByBranch() val count = chapters.countChaptersByBranch()
infoLayout.textViewChapters.isVisible = true infoLayout.textViewChapters.isVisible = true
val chaptersText = resources.getQuantityString(R.plurals.chapters, count, count) val chaptersText = resources.getQuantityString(R.plurals.chapters, count, count)
infoLayout.textViewChapters.text = if (newChapters == 0) { infoLayout.textViewChapters.text = chaptersText
chaptersText
} else {
buildSpannedString {
append(chaptersText)
append(' ')
color(infoLayout.textViewChapters.context.getThemeColor(materialR.attr.colorError)) {
append("(+")
append(newChapters.toString())
append(')')
}
}
}
} }
} }

@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import okio.FileNotFoundException
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
@ -30,6 +31,7 @@ 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.core.prefs.observeAsStateFlow import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.computeSize import org.koitharu.kotatsu.core.util.ext.computeSize
@ -80,7 +82,7 @@ class DetailsViewModel @Inject constructor(
private val mangaId = intent.mangaId private val mangaId = intent.mangaId
private var loadingJob: Job private var loadingJob: Job
val onShowToast = MutableEventFlow<Int>() val onActionDone = MutableEventFlow<ReversibleAction>()
val onShowTip = MutableEventFlow<Unit>() val onShowTip = MutableEventFlow<Unit>()
val onSelectChapter = MutableEventFlow<Long>() val onSelectChapter = MutableEventFlow<Long>()
val onDownloadStarted = MutableEventFlow<Unit>() val onDownloadStarted = MutableEventFlow<Unit>()
@ -234,7 +236,7 @@ class DetailsViewModel @Inject constructor(
fun deleteLocal() { fun deleteLocal() {
val m = details.value?.local?.manga val m = details.value?.local?.manga
if (m == null) { if (m == null) {
onShowToast.call(R.string.file_not_found) errorEvent.call(FileNotFoundException())
return return
} }
launchLoadingJob(Dispatchers.Default) { launchLoadingJob(Dispatchers.Default) {
@ -246,7 +248,7 @@ class DetailsViewModel @Inject constructor(
fun removeBookmark(bookmark: Bookmark) { fun removeBookmark(bookmark: Bookmark) {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {
bookmarksRepository.removeBookmark(bookmark) bookmarksRepository.removeBookmark(bookmark)
onShowToast.call(R.string.bookmark_removed) onActionDone.call(ReversibleAction(R.string.bookmark_removed, null))
} }
} }

@ -5,6 +5,8 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
@ -21,7 +23,6 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.plus
import org.koitharu.kotatsu.core.util.ext.showOrHide import org.koitharu.kotatsu.core.util.ext.showOrHide
import org.koitharu.kotatsu.databinding.FragmentPagesBinding import org.koitharu.kotatsu.databinding.FragmentPagesBinding
import org.koitharu.kotatsu.details.ui.DetailsViewModel import org.koitharu.kotatsu.details.ui.DetailsViewModel
@ -55,9 +56,6 @@ class PagesFragment :
private var scrollListener: ScrollListener? = null private var scrollListener: ScrollListener? = null
private val spanSizeLookup = SpanSizeLookup() private val spanSizeLookup = SpanSizeLookup()
private val listCommitCallback = Runnable {
spanSizeLookup.invalidateCache()
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -66,7 +64,7 @@ class PagesFragment :
detailsViewModel.history, detailsViewModel.history,
detailsViewModel.selectedBranch, detailsViewModel.selectedBranch,
) { details, history, branch -> ) { details, history, branch ->
if (details?.isLoaded == true) { if (details != null && (details.isLoaded || details.chapters.isNotEmpty())) {
PagesViewModel.State(details, history, branch) PagesViewModel.State(details, history, branch)
} else { } else {
null null
@ -89,6 +87,7 @@ class PagesFragment :
with(binding.recyclerView) { with(binding.recyclerView) {
addItemDecoration(TypedListSpacingDecoration(context, false)) addItemDecoration(TypedListSpacingDecoration(context, false))
adapter = thumbnailsAdapter adapter = thumbnailsAdapter
setHasFixedSize(true)
addOnLayoutChangeListener(spanResolver) addOnLayoutChangeListener(spanResolver)
spanResolver?.setGridSize(settings.gridSize / 100f, this) spanResolver?.setGridSize(settings.gridSize / 100f, this)
addOnScrollListener(ScrollListener().also { scrollListener = it }) addOnScrollListener(ScrollListener().also { scrollListener = it })
@ -97,6 +96,7 @@ class PagesFragment :
it.spanCount = checkNotNull(spanResolver).spanCount it.spanCount = checkNotNull(spanResolver).spanCount
} }
} }
detailsViewModel.isChaptersEmpty.observe(viewLifecycleOwner, ::onNoChaptersChanged)
viewModel.thumbnails.observe(viewLifecycleOwner, ::onThumbnailsChanged) viewModel.thumbnails.observe(viewLifecycleOwner, ::onThumbnailsChanged)
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.isLoading.observe(viewLifecycleOwner) { binding.progressBar.showOrHide(it) } viewModel.isLoading.observe(viewLifecycleOwner) { binding.progressBar.showOrHide(it) }
@ -121,7 +121,7 @@ class PagesFragment :
startActivity(intent) startActivity(intent)
} }
private fun onThumbnailsChanged(list: List<ListModel>) { private suspend fun onThumbnailsChanged(list: List<ListModel>) {
val adapter = thumbnailsAdapter ?: return val adapter = thumbnailsAdapter ?: return
if (adapter.itemCount == 0) { if (adapter.itemCount == 0) {
var position = list.indexOfFirst { it is PageThumbnail && it.isCurrent } var position = list.indexOfFirst { it is PageThumbnail && it.isCurrent }
@ -134,12 +134,24 @@ class PagesFragment :
0 0
} }
val scrollCallback = RecyclerViewScrollCallback(requireViewBinding().recyclerView, position, offset) val scrollCallback = RecyclerViewScrollCallback(requireViewBinding().recyclerView, position, offset)
adapter.setItems(list, listCommitCallback + scrollCallback) adapter.emit(list)
scrollCallback.run()
} else { } else {
adapter.setItems(list, listCommitCallback) adapter.emit(list)
} }
} else { } else {
adapter.setItems(list, listCommitCallback) adapter.emit(list)
}
spanSizeLookup.invalidateCache()
viewBinding?.recyclerView?.let {
scrollListener?.postInvalidate(it)
}
}
private fun onNoChaptersChanged(isNoChapters: Boolean) {
with(viewBinding ?: return) {
textViewHolder.isVisible = isNoChapters
recyclerView.isInvisible = isNoChapters
} }
} }

@ -66,7 +66,9 @@ class PagesViewModel @Inject constructor(
private suspend fun doInit(state: State) { private suspend fun doInit(state: State) {
chaptersLoader.init(state.details) chaptersLoader.init(state.details)
val initialChapterId = state.history?.chapterId ?: state.details.allChapters.firstOrNull()?.id ?: return val initialChapterId = state.history?.chapterId ?: state.details.allChapters.firstOrNull()?.id ?: return
chaptersLoader.loadSingleChapter(initialChapterId) if (!chaptersLoader.hasPages(initialChapterId)) {
chaptersLoader.loadSingleChapter(initialChapterId)
}
updateList(state.history) updateList(state.history)
} }

@ -171,6 +171,9 @@ abstract class MangaListFragment :
private suspend fun onListChanged(list: List<ListModel>) { private suspend fun onListChanged(list: List<ListModel>) {
listAdapter?.emit(list) listAdapter?.emit(list)
spanSizeLookup.invalidateCache() spanSizeLookup.invalidateCache()
viewBinding?.recyclerView?.let {
paginationListener?.postInvalidate(it)
}
} }
private fun resolveException(e: Throwable) { private fun resolveException(e: Throwable) {

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.reader.domain package org.koitharu.kotatsu.reader.domain
import androidx.collection.LongSparseArray import androidx.collection.LongSparseArray
import androidx.collection.contains
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
class ChapterPages private constructor(private val pages: ArrayDeque<ReaderPage>) : List<ReaderPage> by pages { class ChapterPages private constructor(private val pages: ArrayDeque<ReaderPage>) : List<ReaderPage> by pages {
@ -57,6 +58,8 @@ class ChapterPages private constructor(private val pages: ArrayDeque<ReaderPage>
return pages.subList(range.first, range.last + 1) return pages.subList(range.first, range.last + 1)
} }
operator fun contains(chapterId: Long) = indices.contains(chapterId)
private fun shiftIndices(delta: Int) { private fun shiftIndices(delta: Int) {
for (i in 0 until indices.size()) { for (i in 0 until indices.size()) {
val range = indices.valueAt(i) val range = indices.valueAt(i)

@ -67,6 +67,10 @@ class ChaptersLoader @Inject constructor(
fun peekChapter(chapterId: Long): MangaChapter? = chapters[chapterId] fun peekChapter(chapterId: Long): MangaChapter? = chapters[chapterId]
fun hasPages(chapterId: Long): Boolean {
return chapterId in chapterPages
}
fun getPages(chapterId: Long): List<ReaderPage> { fun getPages(chapterId: Long): List<ReaderPage> {
return chapterPages.subList(chapterId) return chapterPages.subList(chapterId)
} }

@ -12,6 +12,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:orientation="vertical" android:orientation="vertical"
android:scrollIndicators="top"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_chapter" /> tools:listitem="@layout/item_chapter" />

@ -12,6 +12,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:orientation="vertical" android:orientation="vertical"
android:scrollIndicators="top"
app:bubbleSize="small" app:bubbleSize="small"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
tools:listitem="@layout/item_page_thumb" tools:listitem="@layout/item_page_thumb"

@ -17,13 +17,10 @@
<TextView <TextView
android:id="@+id/textView_state" android:id="@+id/textView_state"
style="@style/Widget.Kotatsu.TextView.Indicator.Vertical"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="center_horizontal"
android:padding="4dp"
android:textSize="12sp"
android:visibility="gone" android:visibility="gone"
tools:drawableTopCompat="@drawable/ic_state_finished" tools:drawableTopCompat="@drawable/ic_state_finished"
tools:text="Completed" tools:text="Completed"
@ -31,13 +28,10 @@
<TextView <TextView
android:id="@+id/textView_chapters" android:id="@+id/textView_chapters"
style="@style/Widget.Kotatsu.TextView.Indicator.Vertical"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="center_horizontal"
android:padding="4dp"
android:textSize="12sp"
android:visibility="gone" android:visibility="gone"
app:drawableTopCompat="@drawable/ic_book_page" app:drawableTopCompat="@drawable/ic_book_page"
tools:text="52 chapters" tools:text="52 chapters"
@ -45,28 +39,22 @@
<TextView <TextView
android:id="@+id/textView_nsfw" android:id="@+id/textView_nsfw"
style="@style/Widget.Kotatsu.TextView.Indicator.Vertical"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="center"
android:padding="4dp"
android:text="@string/nsfw" android:text="@string/nsfw"
android:textSize="12sp"
android:visibility="gone" android:visibility="gone"
app:drawableTopCompat="@drawable/ic_alert_outline" app:drawableTopCompat="@drawable/ic_alert_outline"
tools:visibility="visible" /> tools:visibility="visible" />
<TextView <TextView
android:id="@+id/textView_source" android:id="@+id/textView_source"
style="@style/Widget.Kotatsu.TextView.Indicator.Vertical"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:drawablePadding="4dp"
android:gravity="center"
android:padding="4dp"
android:textSize="12sp"
android:visibility="gone" android:visibility="gone"
app:drawableTopCompat="@drawable/ic_web" app:drawableTopCompat="@drawable/ic_web"
tools:text="Source" tools:text="Source"
@ -74,13 +62,10 @@
<TextView <TextView
android:id="@+id/textView_size" android:id="@+id/textView_size"
style="@style/Widget.Kotatsu.TextView.Indicator.Vertical"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:drawablePadding="4dp"
android:gravity="center"
android:padding="4dp"
android:textSize="12sp"
android:visibility="gone" android:visibility="gone"
app:drawableTopCompat="@drawable/ic_storage" app:drawableTopCompat="@drawable/ic_storage"
tools:text="1.8 GiB" tools:text="1.8 GiB"

@ -81,4 +81,8 @@
<item>@string/frequency_twice_per_month</item> <item>@string/frequency_twice_per_month</item>
<item>@string/frequency_once_per_month</item> <item>@string/frequency_once_per_month</item>
</string-array> </string-array>
<string-array name="details_tabs" translatable="false">
<item>@string/chapters</item>
<item>@string/pages</item>
</string-array>
</resources> </resources>

@ -70,4 +70,8 @@
<item>14</item> <item>14</item>
<item>30</item> <item>30</item>
</string-array> </string-array>
<string-array name="details_tabs_values" translatable="false">
<item>0</item>
<item>1</item>
</string-array>
</resources> </resources>

@ -555,4 +555,5 @@
<string name="rating_safe">Safe</string> <string name="rating_safe">Safe</string>
<string name="rating_suggestive">Suggestive</string> <string name="rating_suggestive">Suggestive</string>
<string name="rating_adult">Adult</string> <string name="rating_adult">Adult</string>
<string name="default_tab">Default tab</string>
</resources> </resources>

@ -220,6 +220,16 @@
<item name="android:textAppearance">?textAppearanceLabelMedium</item> <item name="android:textAppearance">?textAppearanceLabelMedium</item>
</style> </style>
<style name="Widget.Kotatsu.TextView.Indicator.Vertical" parent="Widget.MaterialComponents.TextView">
<item name="android:drawablePadding">4dp</item>
<item name="android:gravity">center</item>
<item name="android:textAlignment">center</item>
<item name="android:padding">4dp</item>
<item name="android:singleLine">true</item>
<item name="android:textSize">12sp</item>
<item name="android:elegantTextHeight">false</item>
</style>
<style name="ThemeOverlay.Kotatsu.MainToolbar" parent=""> <style name="ThemeOverlay.Kotatsu.MainToolbar" parent="">
<item name="colorControlHighlight">@color/selector_overlay</item> <item name="colorControlHighlight">@color/selector_overlay</item>
</style> </style>

@ -46,6 +46,19 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory
android:title="@string/details">
<ListPreference
android:defaultValue="0"
android:entries="@array/details_tabs"
android:entryValues="@array/details_tabs_values"
android:key="details_tab"
android:title="@string/default_tab"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceScreen <PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.nav.NavConfigFragment" android:fragment="org.koitharu.kotatsu.settings.nav.NavConfigFragment"
android:key="nav_main" android:key="nav_main"

Loading…
Cancel
Save