Просмотр исходного кода

增加一个新的紧凑列表布局

HapeLee 1 год назад
Родитель
Сommit
cc4b9c31d2

+ 13 - 6
app/src/main/java/io/legato/kazusa/ui/main/bookshelf/BookshelfConfigBottomSheet.kt

@@ -36,6 +36,7 @@ class BookshelfConfigBottomSheet : BaseBottomSheetDialogFragment(R.layout.dialog
         val isGrid = bookshelfLayout == 1
         val isGrid = bookshelfLayout == 1
         val isGridCompact = bookshelfLayout == 2
         val isGridCompact = bookshelfLayout == 2
         val isGridCover = bookshelfLayout == 3
         val isGridCover = bookshelfLayout == 3
+        val isListCompact = bookshelfLayout == 4
 
 
         val columnCount = requireContext().bookshelfLayoutGrid.takeIf { it > 0 } ?: 1
         val columnCount = requireContext().bookshelfLayoutGrid.takeIf { it > 0 } ?: 1
 
 
@@ -76,13 +77,17 @@ class BookshelfConfigBottomSheet : BaseBottomSheetDialogFragment(R.layout.dialog
             swShowBookshelfFastScroller.isChecked = AppConfig.showBookshelfFastScroller
             swShowBookshelfFastScroller.isChecked = AppConfig.showBookshelfFastScroller
 
 
             chipList.isChecked = isList
             chipList.isChecked = isList
+            chipListCompact.isChecked = isListCompact
             chipGrid.isChecked = isGrid
             chipGrid.isChecked = isGrid
             chipGridCompact.isChecked = isGridCompact
             chipGridCompact.isChecked = isGridCompact
             chipGridCover.isChecked = isGridCover
             chipGridCover.isChecked = isGridCover
-            sliderText.isVisible = isGrid || isGridCompact || isGridCover
-            sliderGridCount.isVisible = isGrid || isGridCompact || isGridCover
+
+            val showSlider = isGrid || isGridCompact || isGridCover
+            sliderText.isVisible = showSlider
+            sliderGridCount.isVisible = showSlider
             sliderGridCount.value = columnCount.toFloat()
             sliderGridCount.value = columnCount.toFloat()
 
 
+
             when (AppConfig.bookshelfSortOrder) {
             when (AppConfig.bookshelfSortOrder) {
                 0 -> binding.chipGroupOrder.check(binding.chipAsc.id)
                 0 -> binding.chipGroupOrder.check(binding.chipAsc.id)
                 1 -> binding.chipGroupOrder.check(binding.chipDesc.id)
                 1 -> binding.chipGroupOrder.check(binding.chipDesc.id)
@@ -90,10 +95,11 @@ class BookshelfConfigBottomSheet : BaseBottomSheetDialogFragment(R.layout.dialog
 
 
             chipGroupLayout.setOnCheckedStateChangeListener { _, checkedIds ->
             chipGroupLayout.setOnCheckedStateChangeListener { _, checkedIds ->
                 val checkedId = checkedIds.firstOrNull()
                 val checkedId = checkedIds.firstOrNull()
-                val showSlider =
-                    checkedId == R.id.chip_grid || checkedId == R.id.chip_grid_compact || checkedId == R.id.chip_grid_cover
-                sliderText.isVisible = showSlider
+                val showSlider = checkedId == R.id.chip_grid ||
+                        checkedId == R.id.chip_grid_compact ||
+                        checkedId == R.id.chip_grid_cover
                 TransitionManager.beginDelayedTransition(root)
                 TransitionManager.beginDelayedTransition(root)
+                sliderText.isVisible = showSlider
                 sliderGridCount.isVisible = showSlider
                 sliderGridCount.isVisible = showSlider
             }
             }
 
 
@@ -136,6 +142,7 @@ class BookshelfConfigBottomSheet : BaseBottomSheetDialogFragment(R.layout.dialog
                     chipGrid.isChecked -> 1
                     chipGrid.isChecked -> 1
                     chipGridCompact.isChecked -> 2
                     chipGridCompact.isChecked -> 2
                     chipGridCover.isChecked -> 3
                     chipGridCover.isChecked -> 3
+                    chipListCompact.isChecked -> 4
                     else -> 0
                     else -> 0
                 }
                 }
 
 
@@ -149,7 +156,7 @@ class BookshelfConfigBottomSheet : BaseBottomSheetDialogFragment(R.layout.dialog
                         AppConfig.bookshelfLayoutGridPortrait = selectedColumn
                         AppConfig.bookshelfLayoutGridPortrait = selectedColumn
                     }
                     }
 
 
-                    if (newLayout == 0) {
+                    if (newLayout == 0 || newLayout == 4) {
                         activityViewModel.booksGridRecycledViewPool.clear()
                         activityViewModel.booksGridRecycledViewPool.clear()
                     } else {
                     } else {
                         activityViewModel.booksListRecycledViewPool.clear()
                         activityViewModel.booksListRecycledViewPool.clear()

+ 8 - 3
app/src/main/java/io/legato/kazusa/ui/main/bookshelf/books/BooksFragment.kt

@@ -39,6 +39,7 @@ import io.legato.kazusa.ui.main.bookshelf.books.styleDefalut.BooksAdapterGrid
 import io.legato.kazusa.ui.main.bookshelf.books.styleDefalut.BooksAdapterGridCompact
 import io.legato.kazusa.ui.main.bookshelf.books.styleDefalut.BooksAdapterGridCompact
 import io.legato.kazusa.ui.main.bookshelf.books.styleDefalut.BooksAdapterGridCover
 import io.legato.kazusa.ui.main.bookshelf.books.styleDefalut.BooksAdapterGridCover
 import io.legato.kazusa.ui.main.bookshelf.books.styleDefalut.BooksAdapterList
 import io.legato.kazusa.ui.main.bookshelf.books.styleDefalut.BooksAdapterList
+import io.legato.kazusa.ui.main.bookshelf.books.styleDefalut.BooksAdapterListCompact
 import io.legato.kazusa.utils.bookshelfLayoutGrid
 import io.legato.kazusa.utils.bookshelfLayoutGrid
 import io.legato.kazusa.utils.bookshelfLayoutMode
 import io.legato.kazusa.utils.bookshelfLayoutMode
 import io.legato.kazusa.utils.cnCompare
 import io.legato.kazusa.utils.cnCompare
@@ -92,9 +93,13 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
                 BooksAdapterGridCompact(requireContext(), this)
                 BooksAdapterGridCompact(requireContext(), this)
             }
             }
 
 
-            else -> {
+            3 -> {
                 BooksAdapterGridCover(requireContext(), this)
                 BooksAdapterGridCover(requireContext(), this)
             }
             }
+
+            else -> {
+                BooksAdapterListCompact(requireContext(), this, this, viewLifecycleOwner.lifecycle)
+            }
         }
         }
     }
     }
     private var booksFlowJob: Job? = null
     private var booksFlowJob: Job? = null
@@ -127,7 +132,7 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
             binding.refreshLayout.isRefreshing = false
             binding.refreshLayout.isRefreshing = false
             activityViewModel.upToc(booksAdapter.getItems())
             activityViewModel.upToc(booksAdapter.getItems())
         }
         }
-        if (bookshelfLayoutMode == 0) {
+        if (bookshelfLayoutMode == 0 ||bookshelfLayoutMode == 4) {
             binding.rvBookshelf.layoutManager = LinearLayoutManager(context)
             binding.rvBookshelf.layoutManager = LinearLayoutManager(context)
             binding.rvBookshelf.setRecycledViewPool(activityViewModel.booksListRecycledViewPool)
             binding.rvBookshelf.setRecycledViewPool(activityViewModel.booksListRecycledViewPool)
         } else {
         } else {
@@ -232,7 +237,7 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
 
 
     private fun startLastUpdateTimeJob() {
     private fun startLastUpdateTimeJob() {
         upLastUpdateTimeJob?.cancel()
         upLastUpdateTimeJob?.cancel()
-        if (!AppConfig.showLastUpdateTime || bookshelfLayoutMode != 0) {
+        if (!AppConfig.showLastUpdateTime || bookshelfLayoutMode != 0 ||bookshelfLayoutMode != 4) {
             return
             return
         }
         }
         upLastUpdateTimeJob = lifecycleScope.launch {
         upLastUpdateTimeJob = lifecycleScope.launch {

+ 7 - 2
app/src/main/java/io/legato/kazusa/ui/main/bookshelf/books/BookshelfFragment2.kt

@@ -36,6 +36,7 @@ import io.legato.kazusa.ui.main.bookshelf.books.styleFold.BooksAdapterGrid
 import io.legato.kazusa.ui.main.bookshelf.books.styleFold.BooksAdapterGridCompact
 import io.legato.kazusa.ui.main.bookshelf.books.styleFold.BooksAdapterGridCompact
 import io.legato.kazusa.ui.main.bookshelf.books.styleFold.BooksAdapterGridCover
 import io.legato.kazusa.ui.main.bookshelf.books.styleFold.BooksAdapterGridCover
 import io.legato.kazusa.ui.main.bookshelf.books.styleFold.BooksAdapterList
 import io.legato.kazusa.ui.main.bookshelf.books.styleFold.BooksAdapterList
+import io.legato.kazusa.ui.main.bookshelf.books.styleFold.BooksAdapterListCompact
 import io.legato.kazusa.utils.bookshelfLayoutGrid
 import io.legato.kazusa.utils.bookshelfLayoutGrid
 import io.legato.kazusa.utils.bookshelfLayoutMode
 import io.legato.kazusa.utils.bookshelfLayoutMode
 import io.legato.kazusa.utils.cnCompare
 import io.legato.kazusa.utils.cnCompare
@@ -85,9 +86,13 @@ class BookshelfFragment2() : BaseBookshelfFragment(R.layout.fragment_bookshelf2)
                 BooksAdapterGridCompact(requireContext(), this)
                 BooksAdapterGridCompact(requireContext(), this)
             }
             }
 
 
-            else -> {
+            3 -> {
                 BooksAdapterGridCover(requireContext(), this)
                 BooksAdapterGridCover(requireContext(), this)
             }
             }
+
+            else -> {
+                BooksAdapterListCompact(requireContext(), this)
+            }
         }
         }
     }
     }
 
 
@@ -111,7 +116,7 @@ class BookshelfFragment2() : BaseBookshelfFragment(R.layout.fragment_bookshelf2)
             binding.refreshLayout.isRefreshing = false
             binding.refreshLayout.isRefreshing = false
             activityViewModel.upToc(books)
             activityViewModel.upToc(books)
         }
         }
-        if (bookshelfLayoutMode == 0) {
+        if (bookshelfLayoutMode == 0 || bookshelfLayoutMode == 4) {
             binding.rvBookshelf.layoutManager = LinearLayoutManager(context)
             binding.rvBookshelf.layoutManager = LinearLayoutManager(context)
         } else {
         } else {
             binding.rvBookshelf.layoutManager = GridLayoutManager(context, bookshelfLayoutGrid)
             binding.rvBookshelf.layoutManager = GridLayoutManager(context, bookshelfLayoutGrid)

+ 116 - 0
app/src/main/java/io/legato/kazusa/ui/main/bookshelf/books/styleDefalut/BooksAdapterListCompact.kt

@@ -0,0 +1,116 @@
+package io.legato.kazusa.ui.main.bookshelf.books.styleDefalut
+
+import android.content.Context
+import android.os.Bundle
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
+import io.legato.kazusa.R
+import io.legato.kazusa.base.adapter.ItemViewHolder
+import io.legato.kazusa.data.entities.Book
+import io.legato.kazusa.databinding.ItemBookshelfListCompactBinding
+import io.legato.kazusa.help.book.isLocal
+import io.legato.kazusa.help.config.AppConfig
+import io.legato.kazusa.utils.toTimeAgo
+import splitties.views.onLongClick
+
+class BooksAdapterListCompact(
+    context: Context,
+    private val fragment: Fragment,
+    private val callBack: CallBack,
+    private val lifecycle: Lifecycle
+) : BaseBooksAdapter<ItemBookshelfListCompactBinding>(context) {
+
+    override fun getViewBinding(parent: ViewGroup): ItemBookshelfListCompactBinding {
+        return ItemBookshelfListCompactBinding.inflate(inflater, parent, false)
+    }
+
+    override fun convert(
+        holder: ItemViewHolder,
+        binding: ItemBookshelfListCompactBinding,
+        item: Book,
+        payloads: MutableList<Any>
+    ) = binding.run {
+        binding.cdCover.transitionName = "book_${item.bookUrl}"
+        if (payloads.isEmpty()) {
+            tvName.text = item.name
+            tvAuthor.text =
+                context.getString(
+                    R.string.author_read,
+                    item.author,
+                    (item.totalChapterNum - item.durChapterIndex).coerceAtLeast(0)
+                )
+            tvLast.text = item.latestChapterTitle
+            ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)
+            upRefresh(binding, item)
+            upLastUpdateTime(binding, item)
+        } else {
+            for (i in payloads.indices) {
+                val bundle = payloads[i] as Bundle
+                bundle.keySet().forEach {
+                    when (it) {
+                        "name" -> tvName.text = item.name
+                        "author" -> tvAuthor.text = item.author
+                        "last" -> tvLast.text = item.latestChapterTitle
+                        "cover" -> ivCover.load(
+                            item.getDisplayCover(),
+                            item.name,
+                            item.author,
+                            false,
+                            item.origin,
+                            fragment,
+                            lifecycle
+                        )
+
+                        "refresh" -> upRefresh(binding, item)
+                        "lastUpdateTime" -> upLastUpdateTime(binding, item)
+                    }
+                }
+            }
+        }
+    }
+
+    private fun upRefresh(binding: ItemBookshelfListCompactBinding, item: Book) {
+        if (!item.isLocal && callBack.isUpdate(item.bookUrl)) {
+            binding.tvUnread.isVisible = false
+            binding.rlLoading.isVisible = true
+        } else {
+            binding.rlLoading.isVisible = false
+            if (AppConfig.showUnread) {
+                //binding.tvUnread.setHighlight(item.lastCheckCount > 0)
+                binding.tvUnread.isVisible = true
+                binding.tvUnread.text = item.getUnreadChapterNum().toString()
+            } else {
+                binding.tvUnread.isVisible = false
+            }
+        }
+    }
+
+    private fun upLastUpdateTime(binding: ItemBookshelfListCompactBinding, item: Book) {
+        if (AppConfig.showLastUpdateTime && !item.isLocal) {
+            val time = item.latestChapterTime.toTimeAgo()
+            if (binding.tvLastUpdateTime.text != time) {
+                binding.tvLastUpdateTime.text = time
+            }
+        } else {
+            binding.tvLastUpdateTime.text = ""
+        }
+    }
+
+    override fun registerListener(holder: ItemViewHolder, binding: ItemBookshelfListCompactBinding) {
+        holder.itemView.apply {
+            binding.cvContent.setOnClickListener {
+                getItem(holder.layoutPosition)?.let {
+                    callBack.open(it, binding.cdCover)
+                }
+            }
+
+            binding.cvContent.onLongClick {
+                getItem(holder.layoutPosition)?.let {
+                    callBack.openBookInfo(it, binding.cdCover)
+                }
+            }
+        }
+    }
+}

+ 3 - 3
app/src/main/java/io/legato/kazusa/ui/main/bookshelf/books/styleFold/BooksAdapterList.kt

@@ -65,9 +65,9 @@ class BooksAdapterList(context: Context, callBack: CallBack) :
             tvLast.text = item.latestChapterTitle
             tvLast.text = item.latestChapterTitle
             ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)
             ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)
             cdUnread.visible()
             cdUnread.visible()
-            ivAuthor.visible()
-            ivLast.visible()
-            ivRead.visible()
+//            ivAuthor.visible()
+//            ivLast.visible()
+//            ivRead.visible()
             upRefresh(this, item)
             upRefresh(this, item)
             upLastUpdateTime(binding, item)
             upLastUpdateTime(binding, item)
         }
         }

+ 194 - 0
app/src/main/java/io/legato/kazusa/ui/main/bookshelf/books/styleFold/BooksAdapterListCompact.kt

@@ -0,0 +1,194 @@
+package io.legato.kazusa.ui.main.bookshelf.books.styleFold
+
+import android.content.Context
+import android.os.Bundle
+import android.view.ViewGroup
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView
+import io.legato.kazusa.R
+import io.legato.kazusa.data.entities.Book
+import io.legato.kazusa.data.entities.BookGroup
+import io.legato.kazusa.databinding.ItemBookshelfListCompactBinding
+import io.legato.kazusa.databinding.ItemBookshelfListCompactGroupBinding
+import io.legato.kazusa.help.book.isLocal
+import io.legato.kazusa.help.config.AppConfig
+import io.legato.kazusa.utils.gone
+import io.legato.kazusa.utils.toTimeAgo
+import io.legato.kazusa.utils.visible
+import splitties.views.onLongClick
+
+@Suppress("UNUSED_PARAMETER")
+class BooksAdapterListCompact(context: Context, callBack: CallBack) :
+    BaseBooksAdapter<RecyclerView.ViewHolder>(context, callBack) {
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+        return when (viewType) {
+            1 -> GroupViewHolder(ItemBookshelfListCompactGroupBinding.inflate(inflater, parent, false))
+            else -> BookViewHolder(ItemBookshelfListCompactBinding.inflate(inflater, parent, false))
+        }
+    }
+
+    override fun onBindViewHolder(
+        holder: RecyclerView.ViewHolder,
+        position: Int,
+        payloads: MutableList<Any>
+    ) {
+        when (holder) {
+            is BookViewHolder -> (getItem(position) as? Book)?.let {
+                holder.registerListener(it)
+                holder.onBind(it, payloads)
+            }
+
+            is GroupViewHolder -> (getItem(position) as? BookGroup)?.let {
+                holder.registerListener(it)
+                holder.onBind(it, payloads)
+            }
+        }
+    }
+
+    private fun getBooksForGroup(group: BookGroup): List<Book> {
+        return if (group.groupId == -1L) {
+            allBooks.take(4)
+        } else {
+            allBooks.filter { (it.group and group.groupId) != 0L }.take(4)
+        }
+    }
+
+    inner class BookViewHolder(val binding: ItemBookshelfListCompactBinding) :
+        RecyclerView.ViewHolder(binding.root) {
+
+        fun onBind(item: Book) = binding.run {
+            tvName.text = item.name
+            tvAuthor.text = item.author
+            tvLast.text = item.latestChapterTitle
+            ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)
+            cdUnread.visible()
+//            ivAuthor.visible()
+//            ivLast.visible()
+//            ivRead.visible()
+            upRefresh(this, item)
+            upLastUpdateTime(binding, item)
+        }
+
+        fun onBind(item: Book, payloads: MutableList<Any>) = binding.run {
+            if (payloads.isEmpty()) {
+                onBind(item)
+            } else {
+                for (i in payloads.indices) {
+                    val bundle = payloads[i] as Bundle
+                    bundle.keySet().forEach {
+                        when (it) {
+                            "name" -> tvName.text = item.name
+                            "author" -> tvAuthor.text = item.author
+                            "last" -> tvLast.text = item.latestChapterTitle
+                            "cover" -> ivCover.load(
+                                item.getDisplayCover(),
+                                item.name,
+                                item.author,
+                                false,
+                                item.origin
+                            )
+
+                            "refresh" -> upRefresh(this, item)
+                            "lastUpdateTime" -> upLastUpdateTime(binding, item)
+                        }
+                    }
+                }
+            }
+        }
+
+        private fun upLastUpdateTime(binding: ItemBookshelfListCompactBinding, item: Book) {
+            if (AppConfig.showLastUpdateTime && !item.isLocal) {
+                val time = item.latestChapterTime.toTimeAgo()
+                if (binding.tvLastUpdateTime.text != time) {
+                    binding.tvLastUpdateTime.text = time
+                }
+            } else {
+                binding.tvLastUpdateTime.text = ""
+            }
+        }
+
+        fun registerListener(item: Any) {
+            binding.cvContent.setOnClickListener {
+                callBack.onItemClick(item, binding.cdCover)
+            }
+            binding.cvContent.onLongClick {
+                callBack.onItemLongClick(item, binding.cdCover)
+            }
+        }
+
+        private fun upRefresh(binding: ItemBookshelfListCompactBinding, item: Book) {
+            if (!item.isLocal && callBack.isUpdate(item.bookUrl)) {
+                binding.tvUnread.isVisible = false
+                binding.rlLoading.isVisible = true
+            } else {
+                binding.rlLoading.isVisible = false
+                if (AppConfig.showUnread) {
+                    //binding.tvUnread.setHighlight(item.lastCheckCount > 0)
+                    binding.tvUnread.isVisible = true
+                    binding.tvUnread.text = item.getUnreadChapterNum().toString()
+                } else {
+                    binding.tvUnread.isVisible = false
+                }
+            }
+        }
+
+    }
+
+    inner class GroupViewHolder(val binding: ItemBookshelfListCompactGroupBinding) :
+        RecyclerView.ViewHolder(binding.root) {
+
+        fun onBind(item: BookGroup) = binding.run {
+            tvName.text = item.groupName
+            loadGroupCover(item)
+            cdUnread.gone()
+        }
+
+
+        fun onBind(item: BookGroup, payloads: MutableList<Any>) = binding.run {
+            if (payloads.isEmpty()) {
+                onBind(item)
+            } else {
+                for (i in payloads.indices) {
+                    val bundle = payloads[i] as Bundle
+                    bundle.keySet().forEach {
+                        when (it) {
+                            "groupName" -> tvName.text = item.groupName
+                            "cover" -> loadGroupCover(item)
+                        }
+                    }
+                }
+            }
+        }
+
+        fun registerListener(item: Any) {
+            binding.cvContent.setOnClickListener {
+                callBack.onItemClick(item, binding.ivCover)
+            }
+            binding.cvContent.onLongClick {
+                callBack.onItemLongClick(item, binding.ivCover)
+            }
+        }
+
+        private fun loadGroupCover(item: BookGroup) = binding.run {
+
+            binding.ivCover.setImageDrawable(
+                ContextCompat.getDrawable(binding.root.context, R.drawable.bg_surface_variant)
+            )
+            binding.ivCover1.setImageDrawable(null)
+            binding.ivCover2.setImageDrawable(null)
+            if (!item.cover.isNullOrEmpty()) {
+                ivCover.load(item.cover)
+            } else {
+
+                val books = getBooksForGroup(item)
+
+                if (books.size > 0) binding.ivCover1.load(books[0].getDisplayCover())
+                if (books.size > 1) binding.ivCover2.load(books[1].getDisplayCover())
+            }
+        }
+
+    }
+
+}

+ 7 - 0
app/src/main/res/layout/dialog_bookshelf_config.xml

@@ -111,6 +111,13 @@
                     android:checkable="true"
                     android:checkable="true"
                     android:text="@string/list" />
                     android:text="@string/list" />
 
 
+                <com.google.android.material.chip.Chip
+                    android:id="@+id/chip_list_compact"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:checkable="true"
+                    android:text="@string/compact_list" />
+
                 <com.google.android.material.chip.Chip
                 <com.google.android.material.chip.Chip
                     android:id="@+id/chip_grid"
                     android:id="@+id/chip_grid"
                     android:layout_width="wrap_content"
                     android:layout_width="wrap_content"

+ 91 - 114
app/src/main/res/layout/item_bookshelf_list.xml

@@ -4,7 +4,6 @@
     xmlns:tools="http://schemas.android.com/tools"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_height="wrap_content"
-    android:layout_marginVertical="4dp"
     app:strokeWidth="0dp">
     app:strokeWidth="0dp">
 
 
     <androidx.constraintlayout.widget.ConstraintLayout
     <androidx.constraintlayout.widget.ConstraintLayout
@@ -85,122 +84,100 @@
             </androidx.constraintlayout.widget.ConstraintLayout>
             </androidx.constraintlayout.widget.ConstraintLayout>
         </com.google.android.material.card.MaterialCardView>
         </com.google.android.material.card.MaterialCardView>
 
 
-
-
-        <TextView
-            android:id="@+id/tv_name"
+        <LinearLayout
             android:layout_width="0dp"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_height="wrap_content"
-            android:scrollbars="none"
-            android:singleLine="true"
-            android:layout_marginTop="4dp"
+            android:orientation="vertical"
             android:layout_marginHorizontal="8dp"
             android:layout_marginHorizontal="8dp"
-            android:text="@string/book_name"
-            style="@style/ChapterNameText"
-            tools:ignore="RtlHardcoded,RtlSymmetry"
-            app:layout_constraintStart_toEndOf="@+id/cd_cover"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/cd_cover" />
-
-        <androidx.appcompat.widget.AppCompatImageView
-            android:id="@+id/iv_author"
-            android:layout_width="18dp"
-            android:layout_height="18dp"
-            android:layout_marginTop="2dp"
-            android:contentDescription="@string/author"
-            android:scrollbars="none"
-            android:src="@drawable/ic_author"
-            tools:ignore="RtlHardcoded,RtlSymmetry"
-            app:layout_constraintStart_toStartOf="@+id/tv_name"
-            app:layout_constraintTop_toBottomOf="@+id/tv_name"/>
-
-        <TextView
-            android:id="@+id/tv_author"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="4dp"
-            android:maxLines="1"
-            android:scrollbars="none"
-            android:singleLine="true"
-            android:text="@string/author"
-            android:textSize="13sp"
-            tools:ignore="RtlSymmetry,TextContrastCheck"
-            app:layout_constraintStart_toEndOf="@+id/iv_author"
-            app:layout_constraintBottom_toTopOf="@+id/iv_author"
-            app:layout_constraintTop_toBottomOf="@+id/iv_author" />
-
-        <TextView
-            android:id="@+id/tv_last_update_time"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginEnd="8dp"
-            android:maxLines="1"
-            android:scrollbars="none"
-            android:singleLine="true"
-            android:text="@string/text"
-            android:textSize="13sp"
-            app:layout_constraintBottom_toBottomOf="@+id/tv_author"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@+id/tv_author" />
-
-        <androidx.appcompat.widget.AppCompatImageView
-            android:id="@+id/iv_read"
-            android:layout_width="18dp"
-            android:layout_height="18dp"
-            android:layout_marginTop="4dp"
-            android:contentDescription="@string/read_dur_progress"
-            android:scrollbars="none"
-            android:src="@drawable/ic_history"
-            tools:ignore="RtlHardcoded,RtlSymmetry"
-            app:layout_constraintStart_toStartOf="@+id/tv_name"
-            app:layout_constraintTop_toBottomOf="@id/tv_author" />
-
-        <TextView
-            android:id="@+id/tv_read"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="4dp"
-            android:layout_marginEnd="8dp"
-            android:layout_weight="1"
-            android:scrollbars="none"
-            android:singleLine="true"
-            android:text="@string/read_dur_progress"
-            android:textSize="13sp"
-            tools:ignore="TextContrastCheck"
-            app:layout_constraintBottom_toTopOf="@id/iv_read"
-            app:layout_constraintStart_toEndOf="@+id/iv_read"
-            app:layout_constraintTop_toBottomOf="@+id/iv_read"
-            app:layout_constraintEnd_toEndOf="parent"/>
-
-        <androidx.appcompat.widget.AppCompatImageView
-            android:id="@+id/iv_last"
-            android:layout_width="18dp"
-            android:layout_height="18dp"
-            android:layout_marginTop="4dp"
-            android:contentDescription="@string/lasted_show"
-            android:scrollbars="none"
-            android:src="@drawable/ic_book_last"
-            tools:ignore="RtlHardcoded,RtlSymmetry"
-            app:layout_constraintStart_toStartOf="@+id/tv_name"
-            app:layout_constraintTop_toBottomOf="@id/iv_read" />
-
-        <TextView
-            android:id="@+id/tv_last"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="4dp"
-            android:layout_marginEnd="8dp"
-            android:layout_weight="1"
-            android:scrollbars="none"
-            android:singleLine="true"
-            android:text="@string/lasted_show"
-            android:textSize="13sp"
-            tools:ignore="TextContrastCheck"
-            app:layout_constraintBottom_toTopOf="@id/iv_last"
-            app:layout_constraintStart_toEndOf="@+id/iv_last"
-            app:layout_constraintTop_toBottomOf="@+id/iv_last"
-            app:layout_constraintEnd_toEndOf="parent"/>
-
+            android:gravity="center_vertical"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@id/cd_cover"
+            app:layout_constraintEnd_toEndOf="parent">
+
+            <TextView
+                android:id="@+id/tv_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="2dp"
+                android:scrollbars="none"
+                android:singleLine="true"
+                android:text="@string/book_name"
+                android:textSize="16sp"
+                android:textStyle="bold"
+                tools:ignore="RtlHardcoded,RtlSymmetry"
+                app:layout_constraintStart_toEndOf="@+id/cd_cover"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="@id/cd_cover" />
+
+            <TextView
+                android:id="@+id/tv_author"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="2dp"
+                android:maxLines="1"
+                android:scrollbars="none"
+                android:singleLine="true"
+                android:text="@string/author"
+                android:textSize="13sp"
+                tools:ignore="RtlSymmetry,TextContrastCheck"
+                app:layout_constraintStart_toEndOf="@+id/iv_author"
+                app:layout_constraintBottom_toTopOf="@+id/iv_author"
+                app:layout_constraintTop_toBottomOf="@+id/iv_author" />
+
+            <TextView
+                android:id="@+id/tv_read"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="2dp"
+                android:scrollbars="none"
+                android:singleLine="true"
+                android:text="@string/read_dur_progress"
+                android:textSize="13sp"
+                tools:ignore="TextContrastCheck"
+                app:layout_constraintBottom_toTopOf="@id/iv_read"
+                app:layout_constraintStart_toEndOf="@+id/iv_read"
+                app:layout_constraintTop_toBottomOf="@+id/iv_read"
+                app:layout_constraintEnd_toEndOf="parent"/>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:id="@+id/tv_last_update_time"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:layout_marginEnd="4dp"
+                    android:maxLines="1"
+                    android:scrollbars="none"
+                    android:singleLine="true"
+                    android:text="@string/text"
+                    android:textSize="13sp"
+                    android:textColor="?attr/colorPrimary"
+                    app:layout_constraintBottom_toTopOf="@id/iv_last"
+                    app:layout_constraintStart_toEndOf="@+id/iv_last"
+                    app:layout_constraintTop_toBottomOf="@+id/iv_last" />
+
+                <TextView
+                    android:id="@+id/tv_last"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:scrollbars="none"
+                    android:singleLine="true"
+                    android:text="@string/lasted_show"
+                    android:textSize="13sp"
+                    tools:ignore="TextContrastCheck"
+                    app:layout_constraintBottom_toTopOf="@id/iv_last"
+                    app:layout_constraintStart_toEndOf="@+id/tv_last_update_time"
+                    app:layout_constraintTop_toBottomOf="@+id/iv_last"
+                    app:layout_constraintEnd_toEndOf="parent"/>
+
+            </LinearLayout>
+
+        </LinearLayout>
 
 
     </androidx.constraintlayout.widget.ConstraintLayout>
     </androidx.constraintlayout.widget.ConstraintLayout>
 
 

+ 169 - 0
app/src/main/res/layout/item_bookshelf_list_compact.xml

@@ -0,0 +1,169 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView 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="wrap_content"
+    app:strokeWidth="0dp">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cv_content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:clickable="true"
+        android:focusable="true"
+        android:background="?attr/selectableItemBackground"
+        tools:ignore="UnusedAttribute">
+
+        <com.google.android.material.card.MaterialCardView
+            android:id="@+id/cd_cover"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:cardCornerRadius="6dp"
+            app:strokeWidth="0dp"
+            android:layout_margin="8dp"
+            android:transitionName="img_cover"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent">
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:id="@+id/cl_cover"
+                android:layout_width="55dp"
+                android:layout_height="77dp">
+
+                <io.legato.kazusa.ui.widget.image.CoverImageView
+                    android:id="@+id/iv_cover"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:scaleType="centerCrop"
+                    android:src="@drawable/image_cover_default"
+                    app:layout_constraintTop_toTopOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    tools:ignore="ImageContrastCheck" />
+
+                <com.google.android.material.progressindicator.LinearProgressIndicator
+                    android:id="@+id/rl_loading"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginVertical="4dp"
+                    android:layout_marginHorizontal="6dp"
+                    android:indeterminate="true"
+                    android:visibility="invisible"
+                    android:layout_gravity="top"
+                    app:layout_constraintTop_toTopOf="@id/iv_cover"
+                    app:layout_constraintStart_toStartOf="@id/iv_cover"
+                    app:layout_constraintEnd_toEndOf="@id/iv_cover" />
+
+                <com.google.android.material.card.MaterialCardView
+                    android:id="@+id/cd_unread"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_margin="4dp"
+                    android:visibility="visible"
+                    app:cardCornerRadius="4dp"
+                    app:strokeWidth="0dp"
+                    app:cardBackgroundColor="?attr/colorSecondaryVariant"
+                    app:layout_constraintTop_toTopOf="@id/iv_cover"
+                    app:layout_constraintEnd_toEndOf="@id/iv_cover">
+
+                    <TextView
+                        android:id="@+id/tv_unread"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:textColor="?attr/colorSurface"
+                        android:textSize="9sp"
+                        android:padding="3dp"
+                        android:textStyle="bold"
+                        android:includeFontPadding="false"
+                        tools:text="5"
+                        tools:ignore="SmallSp" />
+
+                </com.google.android.material.card.MaterialCardView>
+
+            </androidx.constraintlayout.widget.ConstraintLayout>
+        </com.google.android.material.card.MaterialCardView>
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:layout_marginHorizontal="8dp"
+            android:gravity="center_vertical"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@id/cd_cover"
+            app:layout_constraintEnd_toEndOf="parent">
+
+            <TextView
+                android:id="@+id/tv_name"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="2dp"
+                android:scrollbars="none"
+                android:singleLine="true"
+                android:text="@string/book_name"
+                android:textSize="16sp"
+                android:textStyle="bold"
+                tools:ignore="RtlHardcoded,RtlSymmetry"
+                app:layout_constraintStart_toEndOf="@+id/cd_cover"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="@id/cd_cover" />
+
+            <TextView
+                android:id="@+id/tv_author"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="2dp"
+                android:maxLines="1"
+                android:scrollbars="none"
+                android:singleLine="true"
+                android:text="@string/author_read"
+                android:textSize="13sp"
+                tools:ignore="RtlSymmetry,TextContrastCheck"
+                app:layout_constraintStart_toEndOf="@+id/iv_author"
+                app:layout_constraintBottom_toTopOf="@+id/iv_author"
+                app:layout_constraintTop_toBottomOf="@+id/iv_author" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:id="@+id/tv_last_update_time"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:layout_marginEnd="4dp"
+                    android:maxLines="1"
+                    android:scrollbars="none"
+                    android:singleLine="true"
+                    android:text="@string/text"
+                    android:textSize="13sp"
+                    android:textColor="?attr/colorSecondary"
+                    app:layout_constraintBottom_toTopOf="@id/iv_last"
+                    app:layout_constraintStart_toEndOf="@+id/iv_last"
+                    app:layout_constraintTop_toBottomOf="@+id/iv_last" />
+
+                <TextView
+                    android:id="@+id/tv_last"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:scrollbars="none"
+                    android:singleLine="true"
+                    android:text="@string/lasted_show"
+                    android:textSize="13sp"
+                    tools:ignore="TextContrastCheck"
+                    app:layout_constraintBottom_toTopOf="@id/iv_last"
+                    app:layout_constraintStart_toEndOf="@+id/tv_last_update_time"
+                    app:layout_constraintTop_toBottomOf="@+id/iv_last"
+                    app:layout_constraintEnd_toEndOf="parent"/>
+
+            </LinearLayout>
+
+        </LinearLayout>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.google.android.material.card.MaterialCardView>

+ 120 - 0
app/src/main/res/layout/item_bookshelf_list_compact_group.xml

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView 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="wrap_content"
+    app:strokeWidth="0dp">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cv_content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:clickable="true"
+        android:focusable="true"
+        android:background="?attr/selectableItemBackground"
+        tools:ignore="UnusedAttribute">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/cl_cover"
+            android:layout_width="55dp"
+            android:layout_height="77dp"
+            android:layout_marginStart="8dp"
+            android:layout_marginVertical="8dp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent">
+            <!-- 书封面 -->
+            <io.legato.kazusa.ui.widget.image.CoverImageView
+                android:id="@+id/iv_cover"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:scaleType="centerCrop"
+                android:src="?attr/colorSurfaceVariant"
+                android:transitionName="img_cover"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                tools:ignore="ImageContrastCheck" />
+
+            <com.google.android.material.progressindicator.LinearProgressIndicator
+                android:id="@+id/rl_loading"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginVertical="4dp"
+                android:layout_marginHorizontal="6dp"
+                android:indeterminate="true"
+                android:visibility="invisible"
+                android:layout_gravity="top"
+                app:layout_constraintTop_toTopOf="@id/iv_cover"
+                app:layout_constraintStart_toStartOf="@id/iv_cover"
+                app:layout_constraintEnd_toEndOf="@id/iv_cover" />
+
+            <com.google.android.material.card.MaterialCardView
+                android:id="@+id/cd_unread"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="4dp"
+                android:visibility="visible"
+                app:cardCornerRadius="4dp"
+                app:strokeWidth="0dp"
+                app:cardBackgroundColor="?attr/colorSecondaryVariant"
+                app:layout_constraintTop_toTopOf="@id/iv_cover"
+                app:layout_constraintEnd_toEndOf="@id/iv_cover">
+
+                <TextView
+                    android:id="@+id/tv_unread"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textColor="?attr/colorSurface"
+                    android:textSize="10sp"
+                    android:padding="4dp"
+                    android:textStyle="bold"
+                    android:includeFontPadding="false"
+                    tools:text="5"
+                    tools:ignore="SmallSp" />
+
+            </com.google.android.material.card.MaterialCardView>
+
+            <io.legato.kazusa.ui.widget.image.CoverImageView
+                android:id="@+id/iv_cover1"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_marginStart="4dp"
+                android:layout_marginTop="4dp"
+                android:layout_marginEnd="8dp"
+                android:layout_marginBottom="8dp"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintStart_toStartOf="parent" />
+
+            <io.legato.kazusa.ui.widget.image.CoverImageView
+                android:id="@+id/iv_cover2"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_marginBottom="4dp"
+                android:layout_marginEnd="4dp"
+                android:layout_marginTop="8dp"
+                android:layout_marginStart="8dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="parent" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <TextView
+            android:id="@+id/tv_name"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:scrollbars="none"
+            android:layout_marginHorizontal="8dp"
+            android:text="@string/book_name"
+            android:textSize="18sp"
+            android:textStyle="bold"
+            tools:ignore="RtlHardcoded,RtlSymmetry"
+            app:layout_constraintStart_toEndOf="@+id/cl_cover"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="@id/cl_cover"
+            app:layout_constraintBottom_toBottomOf="@id/cl_cover"/>
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.google.android.material.card.MaterialCardView>

+ 0 - 2
app/src/main/res/layout/item_bookshelf_list_group.xml

@@ -4,7 +4,6 @@
     xmlns:tools="http://schemas.android.com/tools"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_height="wrap_content"
-    android:layout_marginVertical="4dp"
     app:strokeWidth="0dp">
     app:strokeWidth="0dp">
 
 
     <androidx.constraintlayout.widget.ConstraintLayout
     <androidx.constraintlayout.widget.ConstraintLayout
@@ -136,7 +135,6 @@
             android:layout_width="0dp"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_height="wrap_content"
             android:scrollbars="none"
             android:scrollbars="none"
-            android:singleLine="true"
             android:layout_marginHorizontal="8dp"
             android:layout_marginHorizontal="8dp"
             android:text="@string/book_name"
             android:text="@string/book_name"
             android:textSize="18sp"
             android:textSize="18sp"

+ 0 - 2
app/src/main/res/layout/view_fastscroller.xml

@@ -37,8 +37,6 @@
             android:layout_width="wrap_content"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="center_horizontal"
             android:layout_gravity="center_horizontal"
-            android:paddingEnd="@dimen/fastscroll_scrollbar_padding_end"
-            android:paddingStart="@dimen/fastscroll_scrollbar_padding_start"
             tools:ignore="ContentDescription"
             tools:ignore="ContentDescription"
             android:src="@drawable/fastscroll_handle" />
             android:src="@drawable/fastscroll_handle" />
 
 

+ 2 - 0
app/src/main/res/values-zh/strings.xml

@@ -1284,4 +1284,6 @@
     <string name="use_animation">使用共享元素动画</string>
     <string name="use_animation">使用共享元素动画</string>
     <string name="reduce_stutter">减少进入阅读界面时的卡顿,但加载书籍时间会延长。</string>
     <string name="reduce_stutter">减少进入阅读界面时的卡顿,但加载书籍时间会延长。</string>
     <string name="delay_book_load">延缓书籍加载</string>
     <string name="delay_book_load">延缓书籍加载</string>
+    <string name="compact_list">紧凑列表</string>
+    <string name="author_read">%s • %d章未读</string>
 </resources>
 </resources>

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -1285,4 +1285,6 @@
     <string name="use_animation">Use Shared Element Animation</string>
     <string name="use_animation">Use Shared Element Animation</string>
     <string name="reduce_stutter">Reduces stutter when entering the reading interface, but increases book loading time.</string>
     <string name="reduce_stutter">Reduces stutter when entering the reading interface, but increases book loading time.</string>
     <string name="delay_book_load">Delay Book Loading</string>
     <string name="delay_book_load">Delay Book Loading</string>
+    <string name="compact_list">Compact List</string>
+    <string name="author_read">%s • %dNot read</string>
 </resources>
 </resources>