[修复] 修复分组添加和编辑、拖动的功能

[修复] 修复关闭分组时直接消失的问题
[修复] 修复发现错误的字符串
[修复] 修复部分设置无法及时触发的问题
[修复] 修复下拉刷新时刷新全部书籍的问题
[修复] 修复不显示未读数量的问题
This commit is contained in:
HapeLee committed 2026-03-19 00:56:30 +08:00
1 parent 1f4a5fb01c
commit d28e9c8a0a
12 files changed
+205 -123

No files matched your search

@@ -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)
}
}
@@ -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)
@@ -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) bookGroupRepository.getByID(groupId) ?: appDb.bookDao.removeGroup(groupId)
appDb.bookGroupDao.insert(bookGroup) 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,7 +54,7 @@ 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()
} }
@@ -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,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( NavigationBar(
modifier = Modifier.regularHazeEffect(state = hazeState), 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
), ),
), ),
@@ -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, start = if (bookshelfLayoutMode != 0) 12.dp else 0.dp,
end = if (uiState.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), verticalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
horizontalArrangement = Arrangement.spacedBy(if (uiState.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, isCompact = BookshelfConfig.bookshelfLayoutCompact,
titleSmallFont = uiState.titleSmallFont, titleSmallFont = BookshelfConfig.bookshelfTitleSmallFont,
titleCenter = uiState.titleCenter, titleCenter = BookshelfConfig.bookshelfTitleCenter,
titleMaxLines = uiState.titleMaxLines, 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, isCompact = BookshelfConfig.bookshelfLayoutCompact,
titleSmallFont = uiState.titleSmallFont, titleSmallFont = BookshelfConfig.bookshelfTitleSmallFont,
titleCenter = uiState.titleCenter, titleCenter = BookshelfConfig.bookshelfTitleCenter,
titleMaxLines = uiState.titleMaxLines, titleMaxLines = BookshelfConfig.bookshelfTitleMaxLines,
coverShadow = uiState.coverShadow, 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, start = if (bookshelfLayoutMode != 0) 12.dp else 0.dp,
end = if (uiState.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), verticalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
horizontalArrangement = Arrangement.spacedBy(if (uiState.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, layoutMode = bookshelfLayoutMode,
isCompact = uiState.bookshelfLayoutCompact, isCompact = BookshelfConfig.bookshelfLayoutCompact,
isUpdating = uiState.updatingBooks.contains(book.bookUrl), isUpdating = uiState.updatingBooks.contains(book.bookUrl),
titleSmallFont = uiState.titleSmallFont, titleSmallFont = BookshelfConfig.bookshelfTitleSmallFont,
titleCenter = uiState.titleCenter, titleCenter = BookshelfConfig.bookshelfTitleCenter,
titleMaxLines = uiState.titleMaxLines, titleMaxLines = BookshelfConfig.bookshelfTitleMaxLines,
coverShadow = uiState.coverShadow, 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,
@@ -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>
@@ -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.compose.runtime.snapshotFlow
import androidx.lifecycle.asFlow
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() val groupsFlow: StateFlow<List<BookGroup>> = bookGroupRepository.flowShow()
.map { it } .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 bookSort = group?.getRealBookSort() ?: BookshelfConfig.bookshelfSort
val isDescending = AppConfig.bookshelfSortOrder == 1 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)
} }
@@ -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,19 +44,36 @@ 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( if (isEditing) {
targetState = isEditing,
transitionSpec = {
fadeIn().togetherWith(fadeOut())
},
label = "GroupManageTransition"
) { editing ->
if (editing) {
GroupEditContent( GroupEditContent(
group = editingGroup, group = editingGroup,
onDismissRequest = { isEditing = false }, onDismissRequest = { isEditing = false },
@@ -67,12 +83,12 @@ fun GroupManageSheet(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = 32.dp) .padding(bottom = 32.dp)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(vertical = 8.dp), .padding(vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -93,18 +109,25 @@ fun GroupManageSheet(
} }
LazyColumn( LazyColumn(
state = listState,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
items(groups, key = { it.groupId }) { group -> items(listData, key = { it.groupId }) { group ->
GroupItem( val manageNameInfo = remember(group) { group.getManageName(context) }
group = group, ReorderableSelectionItem(
onEdit = { 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 editingGroup = group
isEditing = true isEditing = true
},
onToggle = { isChecked ->
viewModel.upGroup(group.copy(show = isChecked))
} }
) )
} }
@@ -112,28 +135,4 @@ fun GroupManageSheet(
} }
} }
} }
}
}
@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
)
}
}
)
} }
@@ -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) },
@@ -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(
@@ -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)
}