[修复] 修复分组添加和编辑、拖动的功能
[修复] 修复关闭分组时直接消失的问题 [修复] 修复发现错误的字符串 [修复] 修复部分设置无法及时触发的问题 [修复] 修复下拉刷新时刷新全部书籍的问题 [修复] 修复不显示未读数量的问题
This commit is contained in:
@@ -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.SvgDecoder
|
||||
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.DirectLinkUploadRepository
|
||||
import io.legado.app.data.repository.ExploreRepository
|
||||
@@ -50,9 +51,11 @@ val appModule = module {
|
||||
single { get<AppDatabase>().readRecordDao }
|
||||
single { get<AppDatabase>().bookDao }
|
||||
single { get<AppDatabase>().bookChapterDao }
|
||||
single { get<AppDatabase>().bookGroupDao }
|
||||
|
||||
singleOf(::ReadRecordRepository)
|
||||
singleOf(::BookRepository)
|
||||
singleOf(::BookGroupRepository)
|
||||
singleOf(::SearchContentRepository)
|
||||
singleOf(::RemoteBookRepository)
|
||||
|
||||
|
||||
@@ -4,12 +4,16 @@ import android.app.Application
|
||||
import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.data.appDb
|
||||
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) {
|
||||
execute {
|
||||
appDb.bookGroupDao.update(*bookGroup)
|
||||
bookGroupRepository.update(*bookGroup)
|
||||
}.onFinally {
|
||||
finally?.invoke()
|
||||
}
|
||||
@@ -23,17 +27,17 @@ class GroupViewModel(application: Application) : BaseViewModel(application) {
|
||||
finally: () -> Unit
|
||||
) {
|
||||
execute {
|
||||
val groupId = appDb.bookGroupDao.getUnusedId()
|
||||
val groupId = bookGroupRepository.getUnusedId()
|
||||
val bookGroup = BookGroup(
|
||||
groupId = groupId,
|
||||
groupName = groupName,
|
||||
cover = cover,
|
||||
bookSort = bookSort,
|
||||
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 {
|
||||
finally()
|
||||
}
|
||||
@@ -41,7 +45,7 @@ class GroupViewModel(application: Application) : BaseViewModel(application) {
|
||||
|
||||
fun delGroup(bookGroup: BookGroup, finally: () -> Unit) {
|
||||
execute {
|
||||
appDb.bookGroupDao.delete(bookGroup)
|
||||
bookGroupRepository.delete(bookGroup)
|
||||
appDb.bookDao.removeGroup(bookGroup.groupId)
|
||||
}.onFinally {
|
||||
finally()
|
||||
@@ -50,10 +54,10 @@ class GroupViewModel(application: Application) : BaseViewModel(application) {
|
||||
|
||||
fun clearCover(bookGroup: BookGroup, finally: () -> Unit) {
|
||||
execute {
|
||||
appDb.bookGroupDao.clearCover(bookGroup.groupId)
|
||||
bookGroupRepository.clearCover(bookGroup.groupId)
|
||||
}.onFinally {
|
||||
finally()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ sealed class MainDestination(
|
||||
|
||||
object Explore : MainDestination(
|
||||
"explore",
|
||||
R.string.screen_find,
|
||||
R.string.discovery,
|
||||
Icons.Outlined.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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
@@ -20,10 +21,10 @@ import androidx.compose.material3.BottomAppBarDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.FlexibleBottomAppBar
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.NavigationBarItemDefaults
|
||||
import androidx.compose.material3.Scaffold
|
||||
@@ -159,8 +160,10 @@ fun MainScreen(
|
||||
modifier = Modifier.weight(1f),
|
||||
bottomBar = {
|
||||
if (!useRail && MainConfig.showBottomView) {
|
||||
FlexibleBottomAppBar(
|
||||
modifier = Modifier.regularHazeEffect(state = hazeState),
|
||||
NavigationBar(
|
||||
modifier = Modifier
|
||||
.regularHazeEffect(state = hazeState)
|
||||
.heightIn(max = 84.dp),
|
||||
containerColor = GlassDefaults.glassColor(
|
||||
noBlurColor = BottomAppBarDefaults.containerColor,
|
||||
blurAlpha = GlassDefaults.DefaultBlurAlpha
|
||||
@@ -180,7 +183,7 @@ fun MainScreen(
|
||||
},
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
indicatorColor = GlassDefaults.glassColor(
|
||||
noBlurColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
noBlurColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
blurAlpha = GlassDefaults.ThickBlurAlpha
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.legado.app.ui.main.bookshelf
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
@@ -48,6 +49,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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(
|
||||
title = title,
|
||||
state = uiState,
|
||||
@@ -271,7 +280,7 @@ fun BookshelfScreen(
|
||||
onRefresh = {
|
||||
scope.launch {
|
||||
isRefreshing = true
|
||||
viewModel.upAllBookToc()
|
||||
viewModel.upToc(uiState.items)
|
||||
delay(1000)
|
||||
isRefreshing = false
|
||||
}
|
||||
@@ -290,26 +299,26 @@ fun BookshelfScreen(
|
||||
) {
|
||||
if (bookGroupStyle == 2 && isInFolderRoot) {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(uiState.bookshelfLayoutGrid.coerceAtLeast(1)),
|
||||
columns = GridCells.Fixed(bookshelfLayoutGrid.coerceAtLeast(1)),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(
|
||||
top = paddingValues.calculateTopPadding(),
|
||||
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 ->
|
||||
if (uiState.bookshelfLayoutMode == 0) {
|
||||
if (bookshelfLayoutMode == 0) {
|
||||
BookGroupItemList(
|
||||
group = group,
|
||||
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 = {
|
||||
val index = uiState.groups.indexOf(group)
|
||||
if (index != -1) {
|
||||
@@ -323,11 +332,11 @@ fun BookshelfScreen(
|
||||
BookGroupItemGrid(
|
||||
group = group,
|
||||
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 = {
|
||||
val index = uiState.groups.indexOf(group)
|
||||
if (index != -1) {
|
||||
@@ -355,6 +364,8 @@ fun BookshelfScreen(
|
||||
paddingValues = paddingValues,
|
||||
books = books,
|
||||
uiState = uiState,
|
||||
bookshelfLayoutMode = bookshelfLayoutMode,
|
||||
bookshelfLayoutGrid = bookshelfLayoutGrid,
|
||||
onBookClick = onBookClick,
|
||||
onBookLongClick = onBookLongClick
|
||||
)
|
||||
@@ -430,31 +441,33 @@ fun BookshelfPage(
|
||||
paddingValues: PaddingValues,
|
||||
books: List<Book>,
|
||||
uiState: BookshelfUiState,
|
||||
bookshelfLayoutMode: Int,
|
||||
bookshelfLayoutGrid: Int,
|
||||
onBookClick: (Book) -> Unit,
|
||||
onBookLongClick: (Book) -> Unit
|
||||
) {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(uiState.bookshelfLayoutGrid.coerceAtLeast(1)),
|
||||
columns = GridCells.Fixed(bookshelfLayoutGrid.coerceAtLeast(1)),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(
|
||||
top = paddingValues.calculateTopPadding(),
|
||||
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 ->
|
||||
BookItem(
|
||||
book = book,
|
||||
layoutMode = uiState.bookshelfLayoutMode,
|
||||
isCompact = uiState.bookshelfLayoutCompact,
|
||||
layoutMode = bookshelfLayoutMode,
|
||||
isCompact = BookshelfConfig.bookshelfLayoutCompact,
|
||||
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) },
|
||||
onLongClick = { onBookLongClick(book) }
|
||||
)
|
||||
@@ -475,7 +488,7 @@ fun BookItem(
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit
|
||||
) {
|
||||
val unreadCount = (book.totalChapterNum - book.durChapterIndex).coerceAtLeast(0)
|
||||
val unreadCount = book.getUnreadChapterNum()
|
||||
|
||||
BookshelfItem(
|
||||
isGrid = layoutMode != 0,
|
||||
@@ -486,7 +499,9 @@ fun BookItem(
|
||||
author = book.author,
|
||||
path = book.getDisplayCover(),
|
||||
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,
|
||||
|
||||
@@ -2,7 +2,6 @@ package io.legado.app.ui.main.bookshelf
|
||||
|
||||
import io.legado.app.data.entities.Book
|
||||
import io.legado.app.data.entities.BookGroup
|
||||
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
|
||||
import io.legado.app.ui.widget.components.list.ListUiState
|
||||
|
||||
data class BookshelfUiState(
|
||||
@@ -15,12 +14,5 @@ data class BookshelfUiState(
|
||||
val groupPreviews: Map<Long, List<Book>> = emptyMap(),
|
||||
val selectedGroupIndex: Int = 0,
|
||||
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()
|
||||
) : ListUiState<Book>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package io.legado.app.ui.main.bookshelf
|
||||
|
||||
import android.app.Application
|
||||
import android.content.res.Configuration
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.gson.stream.JsonWriter
|
||||
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.BookSource
|
||||
import io.legado.app.data.entities.BookSourcePart
|
||||
import io.legado.app.data.repository.BookGroupRepository
|
||||
import io.legado.app.exception.NoStackTraceException
|
||||
import io.legado.app.help.book.BookHelp
|
||||
import io.legado.app.help.book.addType
|
||||
@@ -83,7 +83,10 @@ import java.util.concurrent.Executors
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class BookshelfViewModel(application: Application) : BaseViewModel(application) {
|
||||
class BookshelfViewModel(
|
||||
application: Application,
|
||||
private val bookGroupRepository: BookGroupRepository
|
||||
) : BaseViewModel(application) {
|
||||
var addBookJob: Coroutine<*>? = null
|
||||
|
||||
private val groupIdFlow = MutableStateFlow(BookshelfConfig.saveTabPosition)
|
||||
@@ -104,8 +107,10 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
|
||||
|
||||
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())
|
||||
|
||||
private val allBooksFlow = appDb.bookDao.flowAll().flowOn(Dispatchers.Default)
|
||||
@@ -118,7 +123,8 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
|
||||
}
|
||||
}.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) {
|
||||
groups.associate { group ->
|
||||
val groupBooks = if (group.groupId == BookGroup.IdAll) {
|
||||
@@ -165,8 +171,6 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
|
||||
) || it.author.contains(internal.searchKey, true)
|
||||
}
|
||||
}
|
||||
val isLandscape =
|
||||
context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
|
||||
BookshelfUiState(
|
||||
items = filteredBooks,
|
||||
@@ -178,13 +182,6 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
|
||||
isSearch = internal.searchKey.isNotEmpty(),
|
||||
isLoading = internal.loadingText != null,
|
||||
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
|
||||
)
|
||||
}.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) {
|
||||
upAllBookToc()
|
||||
}
|
||||
@@ -212,8 +220,8 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
|
||||
}
|
||||
|
||||
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) {
|
||||
1 -> if (isDescending) list.sortedByDescending { it.latestChapterTime }
|
||||
@@ -404,7 +412,8 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
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.Column
|
||||
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.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -28,15 +24,18 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.data.entities.BookGroup
|
||||
import io.legado.app.ui.book.group.GroupEditContent
|
||||
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.settingItem.SettingItem
|
||||
import io.legado.app.utils.move
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -45,95 +44,95 @@ fun GroupManageSheet(
|
||||
viewModel: GroupViewModel = 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 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) {
|
||||
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
|
||||
.fillMaxWidth()
|
||||
.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
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ fun ExploreScreen(
|
||||
}
|
||||
|
||||
ListScaffold(
|
||||
title = stringResource(R.string.screen_find),
|
||||
title = stringResource(R.string.discovery),
|
||||
state = uiState,
|
||||
subtitle = uiState.selectedGroup.ifEmpty { stringResource(R.string.all) },
|
||||
onSearchQueryChange = { viewModel.search(it) },
|
||||
|
||||
@@ -60,15 +60,17 @@ fun SelectionItemCard(
|
||||
onEnabledChange: ((Boolean) -> Unit)? = null,
|
||||
onClickEdit: (() -> 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) }
|
||||
|
||||
val containerColor by animateColorAsState(
|
||||
val animatedContainerColor by animateColorAsState(
|
||||
targetValue = if (isSelected)
|
||||
MaterialTheme.colorScheme.secondaryContainer
|
||||
selectedContainerColor ?: MaterialTheme.colorScheme.secondaryContainer
|
||||
else
|
||||
MaterialTheme.colorScheme.surfaceContainerLow,
|
||||
containerColor ?: MaterialTheme.colorScheme.surfaceContainerLow,
|
||||
animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
|
||||
label = "CardColor"
|
||||
)
|
||||
@@ -79,7 +81,7 @@ fun SelectionItemCard(
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
colors = CardDefaults.cardColors(containerColor = containerColor),
|
||||
colors = CardDefaults.cardColors(containerColor = animatedContainerColor),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = elevation)
|
||||
) {
|
||||
Row(
|
||||
@@ -197,7 +199,9 @@ fun LazyItemScope.ReorderableSelectionItem(
|
||||
onEnabledChange: ((Boolean) -> Unit)? = null,
|
||||
onClickEdit: (() -> 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
|
||||
|
||||
@@ -220,6 +224,8 @@ fun LazyItemScope.ReorderableSelectionItem(
|
||||
onClickEdit = onClickEdit,
|
||||
trailingAction = trailingAction,
|
||||
dropdownContent = dropdownContent,
|
||||
containerColor = containerColor,
|
||||
selectedContainerColor = selectedContainerColor,
|
||||
modifier = modifier
|
||||
.zIndex(if (isDragging) 1f else 0f)
|
||||
.then(
|
||||
|
||||
@@ -58,3 +58,9 @@ fun <T> MutableList<T>.removeLastElement(): T {
|
||||
removeAt(lastIndex)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> MutableList<T>.move(fromIndex: Int, toIndex: Int) {
|
||||
if (fromIndex == toIndex) return
|
||||
val element = removeAt(fromIndex)
|
||||
add(toIndex, element)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user