diff --git a/app/src/main/java/io/legado/app/data/repository/BookGroupRepository.kt b/app/src/main/java/io/legado/app/data/repository/BookGroupRepository.kt new file mode 100644 index 000000000..8d32407e7 --- /dev/null +++ b/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> { + return bookGroupDao.flowAll() + } + + fun flowShow(): Flow> { + 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) + } +} diff --git a/app/src/main/java/io/legado/app/di/appModule.kt b/app/src/main/java/io/legado/app/di/appModule.kt index d36d86236..be2b9d925 100644 --- a/app/src/main/java/io/legado/app/di/appModule.kt +++ b/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.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().readRecordDao } single { get().bookDao } single { get().bookChapterDao } + single { get().bookGroupDao } singleOf(::ReadRecordRepository) singleOf(::BookRepository) + singleOf(::BookGroupRepository) singleOf(::SearchContentRepository) singleOf(::RemoteBookRepository) diff --git a/app/src/main/java/io/legado/app/ui/book/group/GroupViewModel.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupViewModel.kt index 2609486c7..4a279992f 100644 --- a/app/src/main/java/io/legado/app/ui/book/group/GroupViewModel.kt +++ b/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.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() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/main/MainDestination.kt b/app/src/main/java/io/legado/app/ui/main/MainDestination.kt index adecaa674..69569d2b0 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainDestination.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainDestination.kt @@ -28,7 +28,7 @@ sealed class MainDestination( object Explore : MainDestination( "explore", - R.string.screen_find, + R.string.discovery, Icons.Outlined.Explore, Icons.Default.Explore ) diff --git a/app/src/main/java/io/legado/app/ui/main/MainScreen.kt b/app/src/main/java/io/legado/app/ui/main/MainScreen.kt index c4e0886c8..953b7d53a 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainScreen.kt +++ b/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.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 ), ), diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt index 751a6c9d6..e9e5b8069 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt @@ -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, 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, diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfUiState.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfUiState.kt index cc4f719e9..b9155af2a 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfUiState.kt +++ b/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.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> = 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 = emptySet() ) : ListUiState diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt index 1811fbbb2..bb6beffd3 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt @@ -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(extraBufferCapacity = 1) - val groupsFlow: StateFlow> = appDb.bookGroupDao.show.asFlow() - .map { it } + val groupsFlow: StateFlow> = bookGroupRepository.flowShow() + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList()) + + val allGroupsFlow: StateFlow> = 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, group: BookGroup?): List { - 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) } diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageSheet.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageSheet.kt index 0e1bd01f1..30ef1362b 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageSheet.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageSheet.kt @@ -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(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 - ) - } - } - ) -} diff --git a/app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt b/app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt index de471cd77..562e0ebbb 100644 --- a/app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/explore/ExploreScreen.kt @@ -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) }, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/card/SelectionItemCard.kt b/app/src/main/java/io/legado/app/ui/widget/components/card/SelectionItemCard.kt index c6375454c..14373157c 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/card/SelectionItemCard.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/card/SelectionItemCard.kt @@ -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( diff --git a/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt b/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt index f64df9239..d7c08938a 100644 --- a/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/CollectionExtensions.kt @@ -58,3 +58,9 @@ fun MutableList.removeLastElement(): T { removeAt(lastIndex) } } + +fun MutableList.move(fromIndex: Int, toIndex: Int) { + if (fromIndex == toIndex) return + val element = removeAt(fromIndex) + add(toIndex, element) +}