目录卷快速跳转与展开 #230、#212、#195
This commit is contained in:
@@ -229,6 +229,70 @@ class ChapterListAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
fun expandAllVolumes() {
|
||||
val all = getItems()
|
||||
var offset = 0
|
||||
|
||||
collapsedVolumes.forEach { volumeIndex ->
|
||||
val volume = all.firstOrNull { it.index == volumeIndex } ?: return@forEach
|
||||
val startIndex = all.indexOf(volume)
|
||||
val currentPos = visibleItems.indexOf(volume)
|
||||
if (currentPos == -1) return@forEach
|
||||
|
||||
val toAdd = mutableListOf<BookChapter>()
|
||||
for (i in startIndex + 1 until all.size) {
|
||||
val next = all[i]
|
||||
if (next.isVolume) break
|
||||
toAdd.add(next)
|
||||
}
|
||||
|
||||
if (toAdd.isNotEmpty()) {
|
||||
visibleItems.addAll(currentPos + 1, toAdd)
|
||||
notifyItemRangeInserted(currentPos + 1, toAdd.size)
|
||||
notifyItemChanged(currentPos)
|
||||
offset += toAdd.size
|
||||
}
|
||||
}
|
||||
|
||||
collapsedVolumes.clear()
|
||||
}
|
||||
|
||||
fun collapseAllVolumes() {
|
||||
val all = getItems()
|
||||
|
||||
all.filter { it.isVolume }.forEach { volume ->
|
||||
val startIndex = visibleItems.indexOf(volume)
|
||||
if (startIndex == -1) return@forEach
|
||||
|
||||
val toRemove = mutableListOf<BookChapter>()
|
||||
for (i in startIndex + 1 until visibleItems.size) {
|
||||
val next = visibleItems[i]
|
||||
if (next.isVolume) break
|
||||
toRemove.add(next)
|
||||
}
|
||||
|
||||
if (toRemove.isNotEmpty()) {
|
||||
visibleItems.removeAll(toRemove)
|
||||
notifyItemRangeRemoved(startIndex + 1, toRemove.size)
|
||||
notifyItemChanged(startIndex)
|
||||
}
|
||||
|
||||
collapsedVolumes.add(volume.index)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllVolumes(): List<String> {
|
||||
return getItems().filter { it.isVolume }.map { it.title }
|
||||
}
|
||||
|
||||
fun getVolumeStartPosition(volumeName: String): Int {
|
||||
return getItems().indexOfFirst { it.isVolume && it.title == volumeName }
|
||||
}
|
||||
|
||||
fun areAllVolumesExpanded(): Boolean {
|
||||
return getItems().filter { it.isVolume }.all { !collapsedVolumes.contains(it.index) }
|
||||
}
|
||||
|
||||
private fun getDisplayTitle(chapter: BookChapter): String =
|
||||
displayTitleMap[chapter.url] ?: chapter.title
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import androidx.fragment.app.activityViewModels
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.transition.platform.MaterialFadeThrough
|
||||
import io.legado.app.R
|
||||
@@ -259,11 +260,47 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
|
||||
}
|
||||
}
|
||||
|
||||
// 可选:清空选择状态并提示
|
||||
adapter.clearSelection()
|
||||
}
|
||||
}
|
||||
|
||||
fun areAllVolumesExpanded(): Boolean = adapter.areAllVolumesExpanded()
|
||||
|
||||
fun toggleAllVolumes() {
|
||||
val allExpanded = adapter.areAllVolumesExpanded()
|
||||
if (allExpanded) {
|
||||
adapter.collapseAllVolumes()
|
||||
toastOnUi("已收起所有卷")
|
||||
} else {
|
||||
adapter.expandAllVolumes()
|
||||
toastOnUi("已展开所有卷")
|
||||
}
|
||||
}
|
||||
|
||||
fun isInSelectionMode(): Boolean = adapter.isInSelectionMode()
|
||||
|
||||
fun toggleChapterSelection() {
|
||||
if (adapter.isInSelectionMode()) {
|
||||
adapter.clearSelection()
|
||||
toastOnUi("已取消选择")
|
||||
} else {
|
||||
adapter.selectAll()
|
||||
toastOnUi("已全选 ${adapter.itemCount} 个章节")
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllVolumes(): List<String> {
|
||||
return adapter.getAllVolumes()
|
||||
}
|
||||
|
||||
fun scrollToVolume(volumeName: String) {
|
||||
val position = adapter.getVolumeStartPosition(volumeName)
|
||||
if (position >= 0) {
|
||||
(binding.recyclerView.layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(position, 0)
|
||||
toastOnUi("已跳转到 $volumeName")
|
||||
}
|
||||
}
|
||||
|
||||
fun scrollToChapter(chapterIndex: Int) {
|
||||
val pos = adapter.getItems().indexOfFirst { it.index == chapterIndex }
|
||||
if (pos != -1) {
|
||||
|
||||
@@ -4,13 +4,24 @@ package io.legado.app.ui.book.toc
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.AbsoluteSizeSpan
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.RelativeSizeSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
@@ -78,6 +89,88 @@ class TocActivity : VMBaseActivity<ActivityChapterListBinding, TocViewModel>(),
|
||||
intent.getStringExtra("bookUrl")?.let {
|
||||
viewModel.initBook(it)
|
||||
}
|
||||
setupTabListener()
|
||||
setupMoreMenuButton()
|
||||
}
|
||||
|
||||
private fun setupTabListener() {
|
||||
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
updateButtonVisibility(tab.position)
|
||||
}
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) {}
|
||||
override fun onTabReselected(tab: TabLayout.Tab) {
|
||||
updateButtonVisibility(tab.position)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateButtonVisibility(tabPosition: Int) {
|
||||
binding.btnChapterMenu.isEnabled = tabPosition == 0
|
||||
}
|
||||
|
||||
private fun setupMoreMenuButton() {
|
||||
val tag = "android:switcher:${binding.viewPager.id}:0"
|
||||
binding.viewPager.postDelayed({
|
||||
val chapterFragment = supportFragmentManager.findFragmentByTag(tag) as? ChapterListFragment
|
||||
val volumes = chapterFragment?.getAllVolumes().orEmpty()
|
||||
binding.btnChapterMenu.isVisible = volumes.isNotEmpty()
|
||||
}, 200)
|
||||
binding.btnChapterMenu.setOnClickListener { view ->
|
||||
showChapterActionsMenu(view)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showChapterActionsMenu(anchor: View) {
|
||||
val popup = PopupMenu(this, anchor)
|
||||
popup.menuInflater.inflate(R.menu.menu_chapter_actions, popup.menu)
|
||||
val tag = "android:switcher:${binding.viewPager.id}:0"
|
||||
val chapterFragment = supportFragmentManager.findFragmentByTag(tag) as? ChapterListFragment
|
||||
val volumes = chapterFragment?.getAllVolumes().orEmpty()
|
||||
volumes.forEachIndexed { index, volumeName ->
|
||||
popup.menu.add(R.id.action_jump_to_volume_section, index, Menu.NONE, volumeName)
|
||||
}
|
||||
val titleItem = popup.menu.findItem(R.id.action_jump_to_volume_section)
|
||||
val s = SpannableString(titleItem.title)
|
||||
s.setSpan(StyleSpan(Typeface.BOLD), 0, s.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
|
||||
s.setSpan(ForegroundColorSpan(Color.GRAY), 0, s.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
|
||||
s.setSpan(AbsoluteSizeSpan(12, true), 0, s.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
|
||||
titleItem.title = s
|
||||
val toggleSelectionItem = popup.menu.findItem(R.id.action_toggle_selection)
|
||||
toggleSelectionItem.title = if (chapterFragment?.isInSelectionMode() == true) {
|
||||
getString(R.string.cancel_select)
|
||||
} else {
|
||||
getString(R.string.select_all)
|
||||
}
|
||||
|
||||
val toggleVolumesItem = popup.menu.findItem(R.id.action_toggle_volumes)
|
||||
toggleVolumesItem.title = if (chapterFragment?.areAllVolumesExpanded() == true) {
|
||||
getString(R.string.coll_volume)
|
||||
} else {
|
||||
getString(R.string.expand_volume)
|
||||
}
|
||||
popup.setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
R.id.action_toggle_volumes -> {
|
||||
chapterFragment?.toggleAllVolumes()
|
||||
true
|
||||
}
|
||||
R.id.action_toggle_selection -> {
|
||||
chapterFragment?.toggleChapterSelection()
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
if (item.groupId == R.id.action_jump_to_volume_section && item.itemId in volumes.indices) {
|
||||
chapterFragment?.scrollToVolume(volumes[item.itemId])
|
||||
true
|
||||
} else false
|
||||
}
|
||||
}
|
||||
}
|
||||
popup.setOnDismissListener {
|
||||
binding.btnChapterMenu.isChecked = false
|
||||
}
|
||||
popup.show()
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp">
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="12dp">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
@@ -37,12 +38,17 @@
|
||||
app:tabMode="fixed" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:id="@+id/btn_chapterMenu"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:visibility="gone"
|
||||
app:iconSize="16dp"
|
||||
android:clickable="true"
|
||||
android:checkable="true"
|
||||
android:layout_gravity="center_vertical"
|
||||
app:icon="@drawable/ic_toc"
|
||||
style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"/>
|
||||
style="@style/Widget.Material3Expressive.Button.IconButton.Outlined"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/action_toggle_volumes"
|
||||
android:title="" />
|
||||
<item
|
||||
android:id="@+id/action_toggle_selection"
|
||||
android:title="" />
|
||||
<item
|
||||
android:id="@+id/action_jump_to_volume_section"
|
||||
android:title="@string/jump_to_volume"
|
||||
android:enabled="false" />
|
||||
</menu>
|
||||
@@ -1326,4 +1326,8 @@
|
||||
<string name="sort_name_asc">名称升序</string>
|
||||
<string name="sort_name_desc">名称降序</string>
|
||||
<string name="text_dottedline">文字下划虚线</string>
|
||||
<string name="jump_to_volume">跳转到卷</string>
|
||||
<string name="cancel_select">取消选择</string>
|
||||
<string name="coll_volume">折叠所有卷</string>
|
||||
<string name="expand_volume">展开所有卷</string>
|
||||
</resources>
|
||||
|
||||
@@ -1327,4 +1327,8 @@
|
||||
<string name="sort_name_asc">名称升序</string>
|
||||
<string name="sort_name_desc">名称降序</string>
|
||||
<string name="text_dottedline">文字下划虚线</string>
|
||||
<string name="jump_to_volume">跳转到卷</string>
|
||||
<string name="cancel_select">取消选择</string>
|
||||
<string name="coll_volume">折叠所有卷</string>
|
||||
<string name="expand_volume">展开所有卷</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user