Branch selection in chapters list
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package org.koitharu.kotatsu.core.model
|
||||
|
||||
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.list.domain.ListFilterOption
|
||||
|
||||
fun ListFilterOption.toChipModel(isChecked: Boolean) = ChipsView.ChipModel(
|
||||
title = titleText,
|
||||
titleResId = titleResId,
|
||||
icon = iconResId,
|
||||
iconData = getIconData(),
|
||||
isChecked = isChecked,
|
||||
data = this,
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.koitharu.kotatsu.core.util
|
||||
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.iterator
|
||||
|
||||
class LocaleStringComparator : Comparator<String?> {
|
||||
|
||||
private val deviceLocales: List<String?>
|
||||
|
||||
init {
|
||||
val localeList = LocaleListCompat.getAdjustedDefault()
|
||||
deviceLocales = buildList(localeList.size() + 1) {
|
||||
add(null)
|
||||
val set = HashSet<String?>(localeList.size() + 1)
|
||||
set.add(null)
|
||||
for (locale in localeList) {
|
||||
val lang = locale.getDisplayLanguage(locale).lowercase()
|
||||
if (set.add(lang)) {
|
||||
add(lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun compare(a: String?, b: String?): Int {
|
||||
val indexA = deviceLocales.indexOf(a?.lowercase())
|
||||
val indexB = deviceLocales.indexOf(b?.lowercase())
|
||||
return when {
|
||||
indexA < 0 && indexB < 0 -> compareValues(a, b)
|
||||
indexA < 0 -> 1
|
||||
indexB < 0 -> -1
|
||||
else -> compareValues(indexA, indexB)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package org.koitharu.kotatsu.details.domain
|
||||
|
||||
import org.koitharu.kotatsu.core.util.LocaleStringComparator
|
||||
import org.koitharu.kotatsu.details.ui.model.MangaBranch
|
||||
|
||||
class BranchComparator : Comparator<MangaBranch> {
|
||||
|
||||
override fun compare(o1: MangaBranch, o2: MangaBranch): Int = compareValues(o1.name, o2.name)
|
||||
private val delegate = LocaleStringComparator()
|
||||
|
||||
override fun compare(o1: MangaBranch, o2: MangaBranch): Int = delegate.compare(o1.name, o2.name)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ fun MangaDetails.mapChapters(
|
||||
return result
|
||||
}
|
||||
|
||||
fun List<ChapterListItem>.withVolumeHeaders(context: Context): List<ListModel> {
|
||||
fun List<ChapterListItem>.withVolumeHeaders(context: Context): MutableList<ListModel> {
|
||||
var prevVolume = 0
|
||||
val result = ArrayList<ListModel>((size * 1.4).toInt())
|
||||
for (item in this) {
|
||||
|
||||
@@ -42,6 +42,7 @@ import coil3.size.Precision
|
||||
import coil3.size.Scale
|
||||
import coil3.transform.RoundedCornersTransformation
|
||||
import coil3.util.CoilUtils
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -167,6 +168,7 @@ class DetailsActivity :
|
||||
TitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView)
|
||||
viewBinding.containerBottomSheet?.let { sheet ->
|
||||
onBackPressedDispatcher.addCallback(BottomSheetCollapseCallback(sheet))
|
||||
BottomSheetBehavior.from(sheet).addBottomSheetCallback(DetailsBottomSheetCallback(viewBinding.swipeRefreshLayout))
|
||||
}
|
||||
TitleExpandListener(viewBinding.textViewTitle).attach()
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import android.view.View
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
|
||||
class DetailsBottomSheetCallback(
|
||||
private val swipeRefreshLayout: SwipeRefreshLayout,
|
||||
) : BottomSheetBehavior.BottomSheetCallback() {
|
||||
|
||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||
swipeRefreshLayout.isEnabled = newState == BottomSheetBehavior.STATE_COLLAPSED
|
||||
}
|
||||
|
||||
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
|
||||
}
|
||||
@@ -19,14 +19,17 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.plus
|
||||
import okio.FileNotFoundException
|
||||
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
||||
import org.koitharu.kotatsu.core.model.toChipModel
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
||||
import org.koitharu.kotatsu.core.util.LocaleStringComparator
|
||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.combine
|
||||
import org.koitharu.kotatsu.core.util.ext.requireValue
|
||||
import org.koitharu.kotatsu.core.util.ext.sortedWithSafe
|
||||
import org.koitharu.kotatsu.details.data.MangaDetails
|
||||
import org.koitharu.kotatsu.details.domain.DetailsInteractor
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
@@ -36,6 +39,7 @@ import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.download.ui.worker.DownloadTask
|
||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
import org.koitharu.kotatsu.list.domain.ListFilterOption
|
||||
import org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
@@ -119,6 +123,18 @@ abstract class ChaptersPagesViewModel(
|
||||
(if (reversed) list.asReversed() else list).filterSearch(query)
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
|
||||
|
||||
val quickFilter = combine(
|
||||
mangaDetails,
|
||||
selectedBranch,
|
||||
) { details, branch ->
|
||||
val branches = details?.chapters?.keys?.sortedWithSafe(LocaleStringComparator()).orEmpty()
|
||||
if (branches.size > 1) {
|
||||
branches.map { ListFilterOption.Branch(it).toChipModel(it == branch) }
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
launchJob(Dispatchers.Default) {
|
||||
localStorageChanges
|
||||
|
||||
@@ -6,10 +6,12 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.ancestors
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.chip.Chip
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
@@ -22,6 +24,7 @@ import org.koitharu.kotatsu.core.ui.dialog.CommonAlertDialogs
|
||||
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.ui.util.PagerNestedScrollHelper
|
||||
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
|
||||
import org.koitharu.kotatsu.core.util.ext.dismissParentDialog
|
||||
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
|
||||
@@ -34,6 +37,7 @@ import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel
|
||||
import org.koitharu.kotatsu.details.ui.withVolumeHeaders
|
||||
import org.koitharu.kotatsu.list.domain.ListFilterOption
|
||||
import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity.IntentBuilder
|
||||
@@ -45,7 +49,7 @@ import kotlin.math.roundToInt
|
||||
@AndroidEntryPoint
|
||||
class ChaptersFragment :
|
||||
BaseFragment<FragmentChaptersBinding>(),
|
||||
OnListItemClickListener<ChapterListItem> {
|
||||
OnListItemClickListener<ChapterListItem>, ChipsView.OnChipClickListener {
|
||||
|
||||
private val viewModel by ChaptersPagesViewModel.ActivityVMLazy(this)
|
||||
|
||||
@@ -86,11 +90,13 @@ class ChaptersFragment :
|
||||
adapter = chaptersAdapter
|
||||
ChapterGridSpanHelper.attach(this)
|
||||
}
|
||||
binding.chipsFilter.onChipClickListener = this
|
||||
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)
|
||||
viewModel.chapters
|
||||
.map { it.withVolumeHeaders(requireContext()) }
|
||||
.flowOn(Dispatchers.Default)
|
||||
.observe(viewLifecycleOwner, this::onChaptersChanged)
|
||||
viewModel.quickFilter.observe(viewLifecycleOwner, this::onFilterChanged)
|
||||
viewModel.isChaptersEmpty.observe(viewLifecycleOwner) {
|
||||
binding.textViewHolder.isVisible = it
|
||||
}
|
||||
@@ -128,6 +134,11 @@ class ChaptersFragment :
|
||||
return selectionController?.onItemContextClick(view, item.chapter.id) ?: false
|
||||
}
|
||||
|
||||
override fun onChipClick(chip: Chip, data: Any?) {
|
||||
if (data !is ListFilterOption.Branch) return
|
||||
viewModel.setSelectedBranch(data.titleText)
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) = Unit
|
||||
|
||||
private fun onChaptersChanged(list: List<ListModel>) {
|
||||
@@ -148,6 +159,13 @@ class ChaptersFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFilterChanged(list: List<ChipsView.ChipModel>) {
|
||||
viewBinding?.chipsFilter?.run {
|
||||
setChips(list)
|
||||
isGone = list.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onSelectChapter(chapterId: Long) {
|
||||
if (!isResumed) {
|
||||
view?.ancestors?.firstNotNullOfOrNull { it as? ViewPager2 }?.setCurrentItem(0, true)
|
||||
|
||||
@@ -60,6 +60,20 @@ sealed interface ListFilterOption {
|
||||
get() = name
|
||||
}
|
||||
|
||||
data class Branch(
|
||||
override val titleText: String?,
|
||||
) : ListFilterOption {
|
||||
|
||||
override val titleResId: Int
|
||||
get() = if (titleText == null) R.string.system_default else 0
|
||||
|
||||
override val iconResId: Int
|
||||
get() = R.drawable.ic_language
|
||||
|
||||
override val groupKey: String
|
||||
get() = "_branch"
|
||||
}
|
||||
|
||||
data class Tag(
|
||||
val tag: MangaTag
|
||||
) : ListFilterOption {
|
||||
|
||||
@@ -3,8 +3,8 @@ package org.koitharu.kotatsu.list.domain
|
||||
import androidx.collection.ArraySet
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.koitharu.kotatsu.core.model.toChipModel
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.list.ui.model.QuickFilter
|
||||
import org.koitharu.kotatsu.parsers.util.suspendlazy.getOrNull
|
||||
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
|
||||
@@ -52,14 +52,7 @@ abstract class MangaListQuickFilter(
|
||||
return null
|
||||
}
|
||||
val availableOptions = availableFilterOptions.getOrNull()?.map { option ->
|
||||
ChipsView.ChipModel(
|
||||
title = option.titleText,
|
||||
titleResId = option.titleResId,
|
||||
icon = option.iconResId,
|
||||
iconData = option.getIconData(),
|
||||
isChecked = option in selectedOptions,
|
||||
data = option,
|
||||
)
|
||||
option.toChipModel(isChecked = option in selectedOptions)
|
||||
}.orEmpty()
|
||||
return if (availableOptions.isNotEmpty()) {
|
||||
QuickFilter(availableOptions)
|
||||
|
||||
@@ -1,15 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/scrollView_filter"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="@dimen/list_spacing"
|
||||
android:scrollbars="none">
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||
android:id="@+id/chips_filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="6dp"
|
||||
app:chipStyle="@style/Widget.Kotatsu.Chip.Filter"
|
||||
app:singleLine="true" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView
|
||||
android:id="@+id/recyclerView_chapters"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_alignWithParentIfMissing="true"
|
||||
android:layout_below="@id/scrollView_filter"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:scrollIndicators="top"
|
||||
@@ -21,7 +50,7 @@
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
@@ -30,7 +59,7 @@
|
||||
android:id="@+id/textView_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginStart="@dimen/margin_normal"
|
||||
android:layout_marginTop="@dimen/margin_normal"
|
||||
android:layout_marginEnd="@dimen/margin_normal"
|
||||
@@ -42,4 +71,4 @@
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</FrameLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
Reference in New Issue
Block a user