ソースを参照

[修复] 修复分组添加和编辑、拖动的功能
[修复] 修复关闭分组时直接消失的问题
[修复] 修复发现错误的字符串
[修复] 修复部分设置无法及时触发的问题
[修复] 修复下拉刷新时刷新全部书籍的问题
[修复] 修复不显示未读数量的问题

HapeLee 7 ヶ月 前
親
コミット
d28e9c8a0a

+ 45 - 0
app/src/main/java/io/legado/app/data/repository/BookGroupRepository.kt

@@ -0,0 +1,45 @@
+package io.legado.app.data.repository
+
+import androidx.lifecycle.asFlow
+import io.legado.app.data.dao.BookGroupDao
+import io.legado.app.data.entities.BookGroup
+import kotlinx.coroutines.flow.Flow
+
+class BookGroupRepository(private val bookGroupDao: BookGroupDao) {
+
+    fun flowAll(): Flow<List<BookGroup>> {
+        return bookGroupDao.flowAll()
+    }
+
+    fun flowShow(): Flow<List<BookGroup>> {
+        return bookGroupDao.show.asFlow()
+    }
+
+    suspend fun update(vararg bookGroup: BookGroup) {
+        bookGroupDao.update(*bookGroup)
+    }
+
+    suspend fun insert(vararg bookGroup: BookGroup) {
+        bookGroupDao.insert(*bookGroup)
+    }
+
+    suspend fun delete(vararg bookGroup: BookGroup) {
+        bookGroupDao.delete(*bookGroup)
+    }
+
+    suspend fun getUnusedId(): Long {
+        return bookGroupDao.getUnusedId()
+    }
+
+    fun getMaxOrder(): Int {
+        return bookGroupDao.maxOrder
+    }
+
+    suspend fun getByID(id: Long): BookGroup? {
+        return bookGroupDao.getByID(id)
+    }
+
+    suspend fun clearCover(groupId: Long) {
+        bookGroupDao.clearCover(groupId)
+    }
+}

+ 3 - 0
app/src/main/java/io/legado/app/di/appModule.kt

@@ -6,6 +6,7 @@ import coil.decode.GifDecoder
 import coil.decode.ImageDecoderDecoder
 import coil.decode.ImageDecoderDecoder
 import coil.decode.SvgDecoder
 import coil.decode.SvgDecoder
 import io.legado.app.data.AppDatabase
 import io.legado.app.data.AppDatabase
+import io.legado.app.data.repository.BookGroupRepository
 import io.legado.app.data.repository.BookRepository
 import io.legado.app.data.repository.BookRepository
 import io.legado.app.data.repository.DirectLinkUploadRepository
 import io.legado.app.data.repository.DirectLinkUploadRepository
 import io.legado.app.data.repository.ExploreRepository
 import io.legado.app.data.repository.ExploreRepository
@@ -50,9 +51,11 @@ val appModule = module {
     single { get<AppDatabase>().readRecordDao }
     single { get<AppDatabase>().readRecordDao }
     single { get<AppDatabase>().bookDao }
     single { get<AppDatabase>().bookDao }
     single { get<AppDatabase>().bookChapterDao }
     single { get<AppDatabase>().bookChapterDao }
+    single { get<AppDatabase>().bookGroupDao }
 
 
     singleOf(::ReadRecordRepository)
     singleOf(::ReadRecordRepository)
     singleOf(::BookRepository)
     singleOf(::BookRepository)
+    singleOf(::BookGroupRepository)
     singleOf(::SearchContentRepository)
     singleOf(::SearchContentRepository)
     singleOf(::RemoteBookRepository)
     singleOf(::RemoteBookRepository)
 
 

+ 13 - 9
app/src/main/java/io/legado/app/ui/book/group/GroupViewModel.kt

@@ -4,12 +4,16 @@ import android.app.Application
 import io.legado.app.base.BaseViewModel
 import io.legado.app.base.BaseViewModel
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookGroup
+import io.legado.app.data.repository.BookGroupRepository
 
 
-class GroupViewModel(application: Application) : BaseViewModel(application) {
+class GroupViewModel(
+    application: Application,
+    private val bookGroupRepository: BookGroupRepository
+) : BaseViewModel(application) {
 
 
     fun upGroup(vararg bookGroup: BookGroup, finally: (() -> Unit)? = null) {
     fun upGroup(vararg bookGroup: BookGroup, finally: (() -> Unit)? = null) {
         execute {
         execute {
-            appDb.bookGroupDao.update(*bookGroup)
+            bookGroupRepository.update(*bookGroup)
         }.onFinally {
         }.onFinally {
             finally?.invoke()
             finally?.invoke()
         }
         }
@@ -23,17 +27,17 @@ class GroupViewModel(application: Application) : BaseViewModel(application) {
         finally: () -> Unit
         finally: () -> Unit
     ) {
     ) {
         execute {
         execute {
-            val groupId = appDb.bookGroupDao.getUnusedId()
+            val groupId = bookGroupRepository.getUnusedId()
             val bookGroup = BookGroup(
             val bookGroup = BookGroup(
                 groupId = groupId,
                 groupId = groupId,
                 groupName = groupName,
                 groupName = groupName,
                 cover = cover,
                 cover = cover,
                 bookSort = bookSort,
                 bookSort = bookSort,
                 enableRefresh = enableRefresh,
                 enableRefresh = enableRefresh,
-                order = appDb.bookGroupDao.maxOrder.plus(1)
+                order = bookGroupRepository.getMaxOrder().plus(1)
             )
             )
-            appDb.bookGroupDao.getByID(groupId) ?: appDb.bookDao.removeGroup(groupId)
-            appDb.bookGroupDao.insert(bookGroup)
+            bookGroupRepository.getByID(groupId) ?: appDb.bookDao.removeGroup(groupId)
+            bookGroupRepository.insert(bookGroup)
         }.onFinally {
         }.onFinally {
             finally()
             finally()
         }
         }
@@ -41,7 +45,7 @@ class GroupViewModel(application: Application) : BaseViewModel(application) {
 
 
     fun delGroup(bookGroup: BookGroup, finally: () -> Unit) {
     fun delGroup(bookGroup: BookGroup, finally: () -> Unit) {
         execute {
         execute {
-            appDb.bookGroupDao.delete(bookGroup)
+            bookGroupRepository.delete(bookGroup)
             appDb.bookDao.removeGroup(bookGroup.groupId)
             appDb.bookDao.removeGroup(bookGroup.groupId)
         }.onFinally {
         }.onFinally {
             finally()
             finally()
@@ -50,10 +54,10 @@ class GroupViewModel(application: Application) : BaseViewModel(application) {
 
 
     fun clearCover(bookGroup: BookGroup, finally: () -> Unit) {
     fun clearCover(bookGroup: BookGroup, finally: () -> Unit) {
         execute {
         execute {
-            appDb.bookGroupDao.clearCover(bookGroup.groupId)
+            bookGroupRepository.clearCover(bookGroup.groupId)
         }.onFinally {
         }.onFinally {
             finally()
             finally()
         }
         }
     }
     }
 
 
-}
+}

+ 1 - 1
app/src/main/java/io/legado/app/ui/main/MainDestination.kt

@@ -28,7 +28,7 @@ sealed class MainDestination(
 
 
     object Explore : MainDestination(
     object Explore : MainDestination(
         "explore",
         "explore",
-        R.string.screen_find,
+        R.string.discovery,
         Icons.Outlined.Explore,
         Icons.Outlined.Explore,
         Icons.Default.Explore
         Icons.Default.Explore
     )
     )

+ 7 - 4
app/src/main/java/io/legado/app/ui/main/MainScreen.kt

@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.rememberPagerState
 import androidx.compose.foundation.pager.rememberPagerState
@@ -20,10 +21,10 @@ import androidx.compose.material3.BottomAppBarDefaults
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.ExtendedFloatingActionButton
 import androidx.compose.material3.ExtendedFloatingActionButton
-import androidx.compose.material3.FlexibleBottomAppBar
 import androidx.compose.material3.Icon
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.NavigationBar
 import androidx.compose.material3.NavigationBarItem
 import androidx.compose.material3.NavigationBarItem
 import androidx.compose.material3.NavigationBarItemDefaults
 import androidx.compose.material3.NavigationBarItemDefaults
 import androidx.compose.material3.Scaffold
 import androidx.compose.material3.Scaffold
@@ -159,8 +160,10 @@ fun MainScreen(
             modifier = Modifier.weight(1f),
             modifier = Modifier.weight(1f),
             bottomBar = {
             bottomBar = {
                 if (!useRail && MainConfig.showBottomView) {
                 if (!useRail && MainConfig.showBottomView) {
-                    FlexibleBottomAppBar(
-                        modifier = Modifier.regularHazeEffect(state = hazeState),
+                    NavigationBar(
+                        modifier = Modifier
+                            .regularHazeEffect(state = hazeState)
+                            .heightIn(max = 84.dp),
                         containerColor = GlassDefaults.glassColor(
                         containerColor = GlassDefaults.glassColor(
                             noBlurColor = BottomAppBarDefaults.containerColor,
                             noBlurColor = BottomAppBarDefaults.containerColor,
                             blurAlpha = GlassDefaults.DefaultBlurAlpha
                             blurAlpha = GlassDefaults.DefaultBlurAlpha
@@ -180,7 +183,7 @@ fun MainScreen(
                                 },
                                 },
                                 colors = NavigationBarItemDefaults.colors(
                                 colors = NavigationBarItemDefaults.colors(
                                     indicatorColor = GlassDefaults.glassColor(
                                     indicatorColor = GlassDefaults.glassColor(
-                                        noBlurColor = MaterialTheme.colorScheme.primaryContainer,
+                                        noBlurColor = MaterialTheme.colorScheme.secondaryContainer,
                                         blurAlpha = GlassDefaults.ThickBlurAlpha
                                         blurAlpha = GlassDefaults.ThickBlurAlpha
                                     ),
                                     ),
                                 ),
                                 ),

+ 44 - 29
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt

@@ -1,5 +1,6 @@
 package io.legado.app.ui.main.bookshelf
 package io.legado.app.ui.main.bookshelf
 
 
+import android.content.res.Configuration
 import androidx.activity.compose.BackHandler
 import androidx.activity.compose.BackHandler
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.ExperimentalFoundationApi
@@ -48,6 +49,7 @@ import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.text.style.TextOverflow
@@ -150,6 +152,13 @@ fun BookshelfScreen(
         }
         }
     }
     }
 
 
+    val configuration = LocalConfiguration.current
+    val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+    val bookshelfLayoutMode =
+        if (isLandscape) BookshelfConfig.bookshelfLayoutModeLandscape else BookshelfConfig.bookshelfLayoutModePortrait
+    val bookshelfLayoutGrid =
+        if (isLandscape) BookshelfConfig.bookshelfLayoutGridLandscape else BookshelfConfig.bookshelfLayoutGridPortrait
+
     ListScaffold(
     ListScaffold(
         title = title,
         title = title,
         state = uiState,
         state = uiState,
@@ -271,7 +280,7 @@ fun BookshelfScreen(
             onRefresh = {
             onRefresh = {
                 scope.launch {
                 scope.launch {
                     isRefreshing = true
                     isRefreshing = true
-                    viewModel.upAllBookToc()
+                    viewModel.upToc(uiState.items)
                     delay(1000)
                     delay(1000)
                     isRefreshing = false
                     isRefreshing = false
                 }
                 }
@@ -290,26 +299,26 @@ fun BookshelfScreen(
         ) {
         ) {
             if (bookGroupStyle == 2 && isInFolderRoot) {
             if (bookGroupStyle == 2 && isInFolderRoot) {
                 LazyVerticalGrid(
                 LazyVerticalGrid(
-                    columns = GridCells.Fixed(uiState.bookshelfLayoutGrid.coerceAtLeast(1)),
+                    columns = GridCells.Fixed(bookshelfLayoutGrid.coerceAtLeast(1)),
                     modifier = Modifier.fillMaxSize(),
                     modifier = Modifier.fillMaxSize(),
                     contentPadding = PaddingValues(
                     contentPadding = PaddingValues(
                         top = paddingValues.calculateTopPadding(),
                         top = paddingValues.calculateTopPadding(),
                         bottom = 120.dp,
                         bottom = 120.dp,
-                        start = if (uiState.bookshelfLayoutMode != 0) 12.dp else 0.dp,
-                        end = if (uiState.bookshelfLayoutMode != 0) 12.dp else 0.dp
+                        start = if (bookshelfLayoutMode != 0) 12.dp else 0.dp,
+                        end = if (bookshelfLayoutMode != 0) 12.dp else 0.dp
                     ),
                     ),
-                    verticalArrangement = Arrangement.spacedBy(if (uiState.bookshelfLayoutMode != 0) 8.dp else 0.dp),
-                    horizontalArrangement = Arrangement.spacedBy(if (uiState.bookshelfLayoutMode != 0) 8.dp else 0.dp)
+                    verticalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
+                    horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp)
                 ) {
                 ) {
                     items(uiState.groups) { group ->
                     items(uiState.groups) { group ->
-                        if (uiState.bookshelfLayoutMode == 0) {
+                        if (bookshelfLayoutMode == 0) {
                             BookGroupItemList(
                             BookGroupItemList(
                                 group = group,
                                 group = group,
                                 previewBooks = uiState.groupPreviews[group.groupId] ?: emptyList(),
                                 previewBooks = uiState.groupPreviews[group.groupId] ?: emptyList(),
-                                isCompact = uiState.bookshelfLayoutCompact,
-                                titleSmallFont = uiState.titleSmallFont,
-                                titleCenter = uiState.titleCenter,
-                                titleMaxLines = uiState.titleMaxLines,
+                                isCompact = BookshelfConfig.bookshelfLayoutCompact,
+                                titleSmallFont = BookshelfConfig.bookshelfTitleSmallFont,
+                                titleCenter = BookshelfConfig.bookshelfTitleCenter,
+                                titleMaxLines = BookshelfConfig.bookshelfTitleMaxLines,
                                 onClick = {
                                 onClick = {
                                     val index = uiState.groups.indexOf(group)
                                     val index = uiState.groups.indexOf(group)
                                     if (index != -1) {
                                     if (index != -1) {
@@ -323,11 +332,11 @@ fun BookshelfScreen(
                             BookGroupItemGrid(
                             BookGroupItemGrid(
                                 group = group,
                                 group = group,
                                 previewBooks = uiState.groupPreviews[group.groupId] ?: emptyList(),
                                 previewBooks = uiState.groupPreviews[group.groupId] ?: emptyList(),
-                                isCompact = uiState.bookshelfLayoutCompact,
-                                titleSmallFont = uiState.titleSmallFont,
-                                titleCenter = uiState.titleCenter,
-                                titleMaxLines = uiState.titleMaxLines,
-                                coverShadow = uiState.coverShadow,
+                                isCompact = BookshelfConfig.bookshelfLayoutCompact,
+                                titleSmallFont = BookshelfConfig.bookshelfTitleSmallFont,
+                                titleCenter = BookshelfConfig.bookshelfTitleCenter,
+                                titleMaxLines = BookshelfConfig.bookshelfTitleMaxLines,
+                                coverShadow = BookshelfConfig.bookshelfCoverShadow,
                                 onClick = {
                                 onClick = {
                                     val index = uiState.groups.indexOf(group)
                                     val index = uiState.groups.indexOf(group)
                                     if (index != -1) {
                                     if (index != -1) {
@@ -355,6 +364,8 @@ fun BookshelfScreen(
                             paddingValues = paddingValues,
                             paddingValues = paddingValues,
                             books = books,
                             books = books,
                             uiState = uiState,
                             uiState = uiState,
+                            bookshelfLayoutMode = bookshelfLayoutMode,
+                            bookshelfLayoutGrid = bookshelfLayoutGrid,
                             onBookClick = onBookClick,
                             onBookClick = onBookClick,
                             onBookLongClick = onBookLongClick
                             onBookLongClick = onBookLongClick
                         )
                         )
@@ -430,31 +441,33 @@ fun BookshelfPage(
     paddingValues: PaddingValues,
     paddingValues: PaddingValues,
     books: List<Book>,
     books: List<Book>,
     uiState: BookshelfUiState,
     uiState: BookshelfUiState,
+    bookshelfLayoutMode: Int,
+    bookshelfLayoutGrid: Int,
     onBookClick: (Book) -> Unit,
     onBookClick: (Book) -> Unit,
     onBookLongClick: (Book) -> Unit
     onBookLongClick: (Book) -> Unit
 ) {
 ) {
     LazyVerticalGrid(
     LazyVerticalGrid(
-        columns = GridCells.Fixed(uiState.bookshelfLayoutGrid.coerceAtLeast(1)),
+        columns = GridCells.Fixed(bookshelfLayoutGrid.coerceAtLeast(1)),
         modifier = Modifier.fillMaxSize(),
         modifier = Modifier.fillMaxSize(),
         contentPadding = PaddingValues(
         contentPadding = PaddingValues(
             top = paddingValues.calculateTopPadding(),
             top = paddingValues.calculateTopPadding(),
             bottom = 120.dp,
             bottom = 120.dp,
-            start = if (uiState.bookshelfLayoutMode != 0) 12.dp else 0.dp,
-            end = if (uiState.bookshelfLayoutMode != 0) 12.dp else 0.dp
+            start = if (bookshelfLayoutMode != 0) 12.dp else 0.dp,
+            end = if (bookshelfLayoutMode != 0) 12.dp else 0.dp
         ),
         ),
-        verticalArrangement = Arrangement.spacedBy(if (uiState.bookshelfLayoutMode != 0) 8.dp else 0.dp),
-        horizontalArrangement = Arrangement.spacedBy(if (uiState.bookshelfLayoutMode != 0) 8.dp else 0.dp)
+        verticalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
+        horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp)
     ) {
     ) {
         items(books, key = { it.bookUrl }) { book ->
         items(books, key = { it.bookUrl }) { book ->
             BookItem(
             BookItem(
                 book = book,
                 book = book,
-                layoutMode = uiState.bookshelfLayoutMode,
-                isCompact = uiState.bookshelfLayoutCompact,
+                layoutMode = bookshelfLayoutMode,
+                isCompact = BookshelfConfig.bookshelfLayoutCompact,
                 isUpdating = uiState.updatingBooks.contains(book.bookUrl),
                 isUpdating = uiState.updatingBooks.contains(book.bookUrl),
-                titleSmallFont = uiState.titleSmallFont,
-                titleCenter = uiState.titleCenter,
-                titleMaxLines = uiState.titleMaxLines,
-                coverShadow = uiState.coverShadow,
+                titleSmallFont = BookshelfConfig.bookshelfTitleSmallFont,
+                titleCenter = BookshelfConfig.bookshelfTitleCenter,
+                titleMaxLines = BookshelfConfig.bookshelfTitleMaxLines,
+                coverShadow = BookshelfConfig.bookshelfCoverShadow,
                 onClick = { onBookClick(book) },
                 onClick = { onBookClick(book) },
                 onLongClick = { onBookLongClick(book) }
                 onLongClick = { onBookLongClick(book) }
             )
             )
@@ -475,7 +488,7 @@ fun BookItem(
     onClick: () -> Unit,
     onClick: () -> Unit,
     onLongClick: () -> Unit
     onLongClick: () -> Unit
 ) {
 ) {
-    val unreadCount = (book.totalChapterNum - book.durChapterIndex).coerceAtLeast(0)
+    val unreadCount = book.getUnreadChapterNum()
 
 
     BookshelfItem(
     BookshelfItem(
         isGrid = layoutMode != 0,
         isGrid = layoutMode != 0,
@@ -486,7 +499,9 @@ fun BookItem(
                 author = book.author,
                 author = book.author,
                 path = book.getDisplayCover(),
                 path = book.getDisplayCover(),
                 isUpdating = isUpdating,
                 isUpdating = isUpdating,
-                modifier = modifier
+                modifier = modifier,
+                badgeText = if (BookshelfConfig.showUnread && unreadCount > 0) unreadCount.toString() else null,
+                showBadgeDot = !BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && unreadCount > 0 && book.lastCheckCount > 0
             )
             )
         },
         },
         title = book.name,
         title = book.name,

+ 0 - 8
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfUiState.kt

@@ -2,7 +2,6 @@ package io.legado.app.ui.main.bookshelf
 
 
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookGroup
-import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
 import io.legado.app.ui.widget.components.list.ListUiState
 import io.legado.app.ui.widget.components.list.ListUiState
 
 
 data class BookshelfUiState(
 data class BookshelfUiState(
@@ -15,12 +14,5 @@ data class BookshelfUiState(
     val groupPreviews: Map<Long, List<Book>> = emptyMap(),
     val groupPreviews: Map<Long, List<Book>> = emptyMap(),
     val selectedGroupIndex: Int = 0,
     val selectedGroupIndex: Int = 0,
     val loadingText: String? = null,
     val loadingText: String? = null,
-    val bookshelfLayoutMode: Int = 1,
-    val bookshelfLayoutGrid: Int = 3,
-    val bookshelfLayoutCompact: Boolean = BookshelfConfig.bookshelfLayoutCompact,
-    val titleSmallFont: Boolean = BookshelfConfig.bookshelfTitleSmallFont,
-    val titleCenter: Boolean = BookshelfConfig.bookshelfTitleCenter,
-    val titleMaxLines: Int = BookshelfConfig.bookshelfTitleMaxLines,
-    val coverShadow: Boolean = BookshelfConfig.bookshelfCoverShadow,
     val updatingBooks: Set<String> = emptySet()
     val updatingBooks: Set<String> = emptySet()
 ) : ListUiState<Book>
 ) : ListUiState<Book>

+ 27 - 18
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt

@@ -1,8 +1,7 @@
 package io.legado.app.ui.main.bookshelf
 package io.legado.app.ui.main.bookshelf
 
 
 import android.app.Application
 import android.app.Application
-import android.content.res.Configuration
-import androidx.lifecycle.asFlow
+import androidx.compose.runtime.snapshotFlow
 import androidx.lifecycle.viewModelScope
 import androidx.lifecycle.viewModelScope
 import com.google.gson.stream.JsonWriter
 import com.google.gson.stream.JsonWriter
 import io.legado.app.R
 import io.legado.app.R
@@ -16,6 +15,7 @@ import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSourcePart
 import io.legado.app.data.entities.BookSourcePart
+import io.legado.app.data.repository.BookGroupRepository
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.book.BookHelp
 import io.legado.app.help.book.BookHelp
 import io.legado.app.help.book.addType
 import io.legado.app.help.book.addType
@@ -83,7 +83,10 @@ import java.util.concurrent.Executors
 import kotlin.math.max
 import kotlin.math.max
 import kotlin.math.min
 import kotlin.math.min
 
 
-class BookshelfViewModel(application: Application) : BaseViewModel(application) {
+class BookshelfViewModel(
+    application: Application,
+    private val bookGroupRepository: BookGroupRepository
+) : BaseViewModel(application) {
     var addBookJob: Coroutine<*>? = null
     var addBookJob: Coroutine<*>? = null
 
 
     private val groupIdFlow = MutableStateFlow(BookshelfConfig.saveTabPosition)
     private val groupIdFlow = MutableStateFlow(BookshelfConfig.saveTabPosition)
@@ -104,8 +107,10 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
 
 
     val scrollTrigger = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
     val scrollTrigger = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
 
 
-    val groupsFlow: StateFlow<List<BookGroup>> = appDb.bookGroupDao.show.asFlow()
-        .map { it }
+    val groupsFlow: StateFlow<List<BookGroup>> = bookGroupRepository.flowShow()
+        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
+
+    val allGroupsFlow: StateFlow<List<BookGroup>> = bookGroupRepository.flowAll()
         .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
         .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
 
 
     private val allBooksFlow = appDb.bookDao.flowAll().flowOn(Dispatchers.Default)
     private val allBooksFlow = appDb.bookDao.flowAll().flowOn(Dispatchers.Default)
@@ -118,7 +123,8 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
             }
             }
         }.flowOn(Dispatchers.Default)
         }.flowOn(Dispatchers.Default)
 
 
-    private val groupPreviewsFlow = combine(groupsFlow, allBooksFlow) { groups, allBooks ->
+    private val groupPreviewsFlow =
+        combine(groupsFlow, allBooksFlow, refreshTrigger) { groups, allBooks, _ ->
         if (BookshelfConfig.bookGroupStyle in 2..3) {
         if (BookshelfConfig.bookGroupStyle in 2..3) {
             groups.associate { group ->
             groups.associate { group ->
                 val groupBooks = if (group.groupId == BookGroup.IdAll) {
                 val groupBooks = if (group.groupId == BookGroup.IdAll) {
@@ -165,8 +171,6 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
                 ) || it.author.contains(internal.searchKey, true)
                 ) || it.author.contains(internal.searchKey, true)
             }
             }
         }
         }
-        val isLandscape =
-            context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
 
 
         BookshelfUiState(
         BookshelfUiState(
             items = filteredBooks,
             items = filteredBooks,
@@ -178,13 +182,6 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
             isSearch = internal.searchKey.isNotEmpty(),
             isSearch = internal.searchKey.isNotEmpty(),
             isLoading = internal.loadingText != null,
             isLoading = internal.loadingText != null,
             loadingText = internal.loadingText,
             loadingText = internal.loadingText,
-            bookshelfLayoutMode = if (isLandscape) BookshelfConfig.bookshelfLayoutModeLandscape else BookshelfConfig.bookshelfLayoutModePortrait,
-            bookshelfLayoutGrid = if (isLandscape) BookshelfConfig.bookshelfLayoutGridLandscape else BookshelfConfig.bookshelfLayoutGridPortrait,
-            bookshelfLayoutCompact = BookshelfConfig.bookshelfLayoutCompact,
-            titleSmallFont = BookshelfConfig.bookshelfTitleSmallFont,
-            titleCenter = BookshelfConfig.bookshelfTitleCenter,
-            titleMaxLines = BookshelfConfig.bookshelfTitleMaxLines,
-            coverShadow = BookshelfConfig.bookshelfCoverShadow,
             updatingBooks = internal.updatingBooks
             updatingBooks = internal.updatingBooks
         )
         )
     }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), BookshelfUiState())
     }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), BookshelfUiState())
@@ -201,6 +198,17 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
             }
             }
         }
         }
 
 
+        // 监听排序配置变化,触发刷新
+        viewModelScope.launch {
+            snapshotFlow { BookshelfConfig.bookshelfSort }.collect { refresh() }
+        }
+        viewModelScope.launch {
+            snapshotFlow { BookshelfConfig.bookshelfSortOrder }.collect { refresh() }
+        }
+        viewModelScope.launch {
+            snapshotFlow { BookshelfConfig.bookGroupStyle }.collect { refresh() }
+        }
+
         if (BookshelfConfig.autoRefreshBook) {
         if (BookshelfConfig.autoRefreshBook) {
             upAllBookToc()
             upAllBookToc()
         }
         }
@@ -212,8 +220,8 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
     }
     }
 
 
     private fun sortBooks(list: List<Book>, group: BookGroup?): List<Book> {
     private fun sortBooks(list: List<Book>, group: BookGroup?): List<Book> {
-        val bookSort = group?.getRealBookSort() ?: AppConfig.bookshelfSort
-        val isDescending = AppConfig.bookshelfSortOrder == 1
+        val bookSort = group?.getRealBookSort() ?: BookshelfConfig.bookshelfSort
+        val isDescending = BookshelfConfig.bookshelfSortOrder == 1
 
 
         return when (bookSort) {
         return when (bookSort) {
             1 -> if (isDescending) list.sortedByDescending { it.latestChapterTime }
             1 -> if (isDescending) list.sortedByDescending { it.latestChapterTime }
@@ -404,7 +412,8 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
     }
     }
 
 
     private fun postUpBooksCount() {
     private fun postUpBooksCount() {
-        val count = if (AppConfig.showWaitUpCount) waitUpTocBooks.size + onUpTocBooks.size else 0
+        val count =
+            if (BookshelfConfig.showWaitUpCount) waitUpTocBooks.size + onUpTocBooks.size else 0
         FlowEventBus.post(EventBus.UP_BOOKSHELF_COUNT, count)
         FlowEventBus.post(EventBus.UP_BOOKSHELF_COUNT, count)
     }
     }
 
 

+ 81 - 82
app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageSheet.kt

@@ -1,9 +1,5 @@
 package io.legado.app.ui.main.bookshelf
 package io.legado.app.ui.main.bookshelf
 
 
-import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
-import androidx.compose.animation.togetherWith
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Row
@@ -11,16 +7,16 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Edit
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Switch
 import androidx.compose.material3.Text
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.mutableStateOf
@@ -28,15 +24,18 @@ import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.ui.book.group.GroupEditContent
 import io.legado.app.ui.book.group.GroupEditContent
 import io.legado.app.ui.book.group.GroupViewModel
 import io.legado.app.ui.book.group.GroupViewModel
+import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
 import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
 import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet
-import io.legado.app.ui.widget.components.settingItem.SettingItem
+import io.legado.app.utils.move
 import org.koin.androidx.compose.koinViewModel
 import org.koin.androidx.compose.koinViewModel
+import sh.calvin.reorderable.rememberReorderableLazyListState
 
 
 @OptIn(ExperimentalMaterial3Api::class)
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 @Composable
@@ -45,95 +44,95 @@ fun GroupManageSheet(
     viewModel: GroupViewModel = koinViewModel(),
     viewModel: GroupViewModel = koinViewModel(),
     bookshelfViewModel: BookshelfViewModel = koinViewModel()
     bookshelfViewModel: BookshelfViewModel = koinViewModel()
 ) {
 ) {
-    val groups by bookshelfViewModel.groupsFlow.collectAsState(initial = emptyList())
+    val context = LocalContext.current
+    val groups by bookshelfViewModel.allGroupsFlow.collectAsState()
     var editingGroup by remember { mutableStateOf<BookGroup?>(null) }
     var editingGroup by remember { mutableStateOf<BookGroup?>(null) }
     var isEditing by remember { mutableStateOf(false) }
     var isEditing by remember { mutableStateOf(false) }
 
 
+    var listData by remember { mutableStateOf(groups) }
+    val listState = rememberLazyListState()
+    val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
+        listData = listData.toMutableList().apply {
+            move(from.index, to.index)
+        }
+    }
+
+    LaunchedEffect(groups) {
+        if (!reorderableState.isAnyItemDragging) {
+            listData = groups
+        }
+    }
+
+    LaunchedEffect(reorderableState.isAnyItemDragging) {
+        if (!reorderableState.isAnyItemDragging) {
+            val updatedGroups = listData.mapIndexed { index, group ->
+                group.copy(order = index)
+            }
+            viewModel.upGroup(*updatedGroups.toTypedArray())
+        }
+    }
+
     GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
     GlassModalBottomSheet(onDismissRequest = onDismissRequest) {
-        AnimatedContent(
-            targetState = isEditing,
-            transitionSpec = {
-                fadeIn().togetherWith(fadeOut())
-            },
-            label = "GroupManageTransition"
-        ) { editing ->
-            if (editing) {
-                GroupEditContent(
-                    group = editingGroup,
-                    onDismissRequest = { isEditing = false },
-                    viewModel = viewModel
-                )
-            } else {
-                Column(
+        if (isEditing) {
+            GroupEditContent(
+                group = editingGroup,
+                onDismissRequest = { isEditing = false },
+                viewModel = viewModel
+            )
+        } else {
+            Column(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(bottom = 32.dp)
+            ) {
+                Row(
                     modifier = Modifier
                     modifier = Modifier
                         .fillMaxWidth()
                         .fillMaxWidth()
                         .padding(horizontal = 16.dp)
                         .padding(horizontal = 16.dp)
-                        .padding(bottom = 32.dp)
+                        .padding(vertical = 8.dp),
+                    horizontalArrangement = Arrangement.SpaceBetween,
+                    verticalAlignment = Alignment.CenterVertically
                 ) {
                 ) {
-                    Row(
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .padding(vertical = 8.dp),
-                        horizontalArrangement = Arrangement.SpaceBetween,
-                        verticalAlignment = Alignment.CenterVertically
-                    ) {
-                        Text(
-                            text = stringResource(R.string.group_manage),
-                            style = MaterialTheme.typography.titleLarge
+                    Text(
+                        text = stringResource(R.string.group_manage),
+                        style = MaterialTheme.typography.titleLarge
+                    )
+                    IconButton(onClick = {
+                        editingGroup = null
+                        isEditing = true
+                    }) {
+                        Icon(
+                            Icons.Default.Add,
+                            contentDescription = stringResource(R.string.add)
                         )
                         )
-                        IconButton(onClick = {
-                            editingGroup = null
-                            isEditing = true
-                        }) {
-                            Icon(
-                                Icons.Default.Add,
-                                contentDescription = stringResource(R.string.add)
-                            )
-                        }
                     }
                     }
+                }
 
 
-                    LazyColumn(
-                        modifier = Modifier.fillMaxWidth(),
-                        verticalArrangement = Arrangement.spacedBy(8.dp)
-                    ) {
-                        items(groups, key = { it.groupId }) { group ->
-                            GroupItem(
-                                group = group,
-                                onEdit = {
-                                    editingGroup = group
-                                    isEditing = true
-                                },
-                                onToggle = { isChecked ->
-                                    viewModel.upGroup(group.copy(show = isChecked))
-                                }
-                            )
-                        }
+                LazyColumn(
+                    state = listState,
+                    modifier = Modifier.fillMaxWidth(),
+                    verticalArrangement = Arrangement.spacedBy(8.dp)
+                ) {
+                    items(listData, key = { it.groupId }) { group ->
+                        val manageNameInfo = remember(group) { group.getManageName(context) }
+                        ReorderableSelectionItem(
+                            state = reorderableState,
+                            key = group.groupId,
+                            title = group.groupName.ifBlank { manageNameInfo.suffix.orEmpty() },
+                            subtitle = if (group.groupName.isNotBlank()) manageNameInfo.suffix else null,
+                            isEnabled = group.show,
+                            containerColor = MaterialTheme.colorScheme.surface,
+                            onEnabledChange = { isChecked ->
+                                viewModel.upGroup(group.copy(show = isChecked))
+                            },
+                            onClickEdit = {
+                                editingGroup = group
+                                isEditing = true
+                            }
+                        )
                     }
                     }
                 }
                 }
             }
             }
         }
         }
     }
     }
 }
 }
-
-@Composable
-private fun GroupItem(
-    group: BookGroup,
-    onEdit: () -> Unit,
-    onToggle: (Boolean) -> Unit
-) {
-    SettingItem(
-        title = group.groupName,
-        onClick = onEdit,
-        trailingContent = {
-            Row(verticalAlignment = Alignment.CenterVertically) {
-                IconButton(onClick = onEdit) {
-                    Icon(Icons.Default.Edit, contentDescription = stringResource(R.string.edit))
-                }
-                Switch(
-                    checked = group.show,
-                    onCheckedChange = onToggle
-                )
-            }
-        }
-    )
-}

+ 1 - 1
app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt

@@ -139,7 +139,7 @@ fun ExploreScreen(
     }
     }
 
 
     ListScaffold(
     ListScaffold(
-        title = stringResource(R.string.screen_find),
+        title = stringResource(R.string.discovery),
         state = uiState,
         state = uiState,
         subtitle = uiState.selectedGroup.ifEmpty { stringResource(R.string.all) },
         subtitle = uiState.selectedGroup.ifEmpty { stringResource(R.string.all) },
         onSearchQueryChange = { viewModel.search(it) },
         onSearchQueryChange = { viewModel.search(it) },

+ 12 - 6
app/src/main/java/io/legado/app/ui/widget/components/card/SelectionItemCard.kt

@@ -60,15 +60,17 @@ fun SelectionItemCard(
     onEnabledChange: ((Boolean) -> Unit)? = null,
     onEnabledChange: ((Boolean) -> Unit)? = null,
     onClickEdit: (() -> Unit)? = null,
     onClickEdit: (() -> Unit)? = null,
     trailingAction: @Composable (RowScope.() -> Unit)? = null,
     trailingAction: @Composable (RowScope.() -> Unit)? = null,
-    dropdownContent: @Composable (ColumnScope.(onDismiss: () -> Unit) -> Unit)? = null
+    dropdownContent: @Composable (ColumnScope.(onDismiss: () -> Unit) -> Unit)? = null,
+    containerColor: Color? = null,
+    selectedContainerColor: Color? = null
 ) {
 ) {
     var showMenu by remember { mutableStateOf(false) }
     var showMenu by remember { mutableStateOf(false) }
 
 
-    val containerColor by animateColorAsState(
+    val animatedContainerColor by animateColorAsState(
         targetValue = if (isSelected)
         targetValue = if (isSelected)
-            MaterialTheme.colorScheme.secondaryContainer
+            selectedContainerColor ?: MaterialTheme.colorScheme.secondaryContainer
         else
         else
-            MaterialTheme.colorScheme.surfaceContainerLow,
+            containerColor ?: MaterialTheme.colorScheme.surfaceContainerLow,
         animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
         animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
         label = "CardColor"
         label = "CardColor"
     )
     )
@@ -79,7 +81,7 @@ fun SelectionItemCard(
             .padding(horizontal = 16.dp)
             .padding(horizontal = 16.dp)
             .fillMaxWidth(),
             .fillMaxWidth(),
         shape = MaterialTheme.shapes.medium,
         shape = MaterialTheme.shapes.medium,
-        colors = CardDefaults.cardColors(containerColor = containerColor),
+        colors = CardDefaults.cardColors(containerColor = animatedContainerColor),
         elevation = CardDefaults.cardElevation(defaultElevation = elevation)
         elevation = CardDefaults.cardElevation(defaultElevation = elevation)
     ) {
     ) {
         Row(
         Row(
@@ -197,7 +199,9 @@ fun LazyItemScope.ReorderableSelectionItem(
     onEnabledChange: ((Boolean) -> Unit)? = null,
     onEnabledChange: ((Boolean) -> Unit)? = null,
     onClickEdit: (() -> Unit)? = null,
     onClickEdit: (() -> Unit)? = null,
     trailingAction: @Composable (RowScope.() -> Unit)? = null,
     trailingAction: @Composable (RowScope.() -> Unit)? = null,
-    dropdownContent: @Composable (ColumnScope.(onDismiss: () -> Unit) -> Unit)? = null
+    dropdownContent: @Composable (ColumnScope.(onDismiss: () -> Unit) -> Unit)? = null,
+    containerColor: Color? = null,
+    selectedContainerColor: Color? = null
 ) {
 ) {
     val hapticFeedback = LocalHapticFeedback.current
     val hapticFeedback = LocalHapticFeedback.current
 
 
@@ -220,6 +224,8 @@ fun LazyItemScope.ReorderableSelectionItem(
             onClickEdit = onClickEdit,
             onClickEdit = onClickEdit,
             trailingAction = trailingAction,
             trailingAction = trailingAction,
             dropdownContent = dropdownContent,
             dropdownContent = dropdownContent,
+            containerColor = containerColor,
+            selectedContainerColor = selectedContainerColor,
             modifier = modifier
             modifier = modifier
                 .zIndex(if (isDragging) 1f else 0f)
                 .zIndex(if (isDragging) 1f else 0f)
                 .then(
                 .then(

+ 6 - 0
app/src/main/java/io/legado/app/utils/CollectionExtensions.kt

@@ -58,3 +58,9 @@ fun <T> MutableList<T>.removeLastElement(): T {
         removeAt(lastIndex)
         removeAt(lastIndex)
     }
     }
 }
 }
+
+fun <T> MutableList<T>.move(fromIndex: Int, toIndex: Int) {
+    if (fromIndex == toIndex) return
+    val element = removeAt(fromIndex)
+    add(toIndex, element)
+}