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 a15503ce2..80359bc81 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 @@ -1,5 +1,7 @@ package io.legado.app.ui.main +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -8,10 +10,12 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.MenuOpen +import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Search import androidx.compose.material3.Badge @@ -33,11 +37,16 @@ import androidx.compose.material3.WideNavigationRailValue import androidx.compose.material3.rememberWideNavigationRailState 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 import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -48,18 +57,24 @@ import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.config.mainConfig.MainConfig import io.legado.app.ui.main.bookshelf.BookshelfScreen +import io.legado.app.ui.main.bookshelf.BookshelfViewModel import io.legado.app.ui.main.explore.ExploreScreen import io.legado.app.ui.main.my.MyScreen import io.legado.app.ui.main.rss.RssScreen import io.legado.app.ui.theme.regularHazeEffect import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.GlassDefaults +import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu +import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem import io.legado.app.utils.startActivity import io.legado.app.utils.startActivityForBook import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@OptIn( + ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class, + ExperimentalFoundationApi::class +) @Composable fun MainScreen( viewModel: MainViewModel = koinViewModel(), @@ -69,6 +84,9 @@ fun MainScreen( val uiState by viewModel.state.collectAsStateWithLifecycle() val coroutineScope = rememberCoroutineScope() + val bookshelfViewModel: BookshelfViewModel = koinViewModel() + val bookshelfUiState by bookshelfViewModel.uiState.collectAsState() + val hazeState = remember { HazeState() } val destinations = remember(MainConfig.showDiscovery, MainConfig.showRSS) { MainDestination.mainDestinations.filter { @@ -139,6 +157,9 @@ fun MainScreen( val labelVisibilityMode = MainConfig.labelVisibilityMode destinations.forEachIndexed { index, destination -> val selected = pagerState.targetPage == index + var showGroupMenu by remember { mutableStateOf(false) } + val haptic = LocalHapticFeedback.current + WideNavigationRailItem( railExpanded = navState.targetValue == WideNavigationRailValue.Expanded, selected = selected, @@ -148,7 +169,57 @@ fun MainScreen( } }, icon = { - NavigationIcon(destination, selected, uiState.upBooksCount) + Box { + NavigationIcon( + destination = destination, + selected = selected, + upBooksCount = uiState.upBooksCount, + modifier = if (destination == MainDestination.Bookshelf) { + Modifier.combinedClickable( + onClick = { + coroutineScope.launch { + pagerState.animateScrollToPage(index) + } + }, + onLongClick = { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + showGroupMenu = true + } + ) + } else Modifier + ) + + if (destination == MainDestination.Bookshelf && showGroupMenu) { + RoundDropdownMenu( + expanded = showGroupMenu, + onDismissRequest = { showGroupMenu = false } + ) { dismiss -> + bookshelfUiState.groups.forEachIndexed { groupIndex, group -> + RoundDropdownMenuItem( + text = { Text(group.groupName) }, + onClick = { + coroutineScope.launch { + if (pagerState.currentPage != index) { + pagerState.scrollToPage(index) + } + bookshelfViewModel.changeGroup(group.groupId) + dismiss() + } + }, + trailingIcon = { + if (bookshelfUiState.selectedGroupIndex == groupIndex) { + Icon( + Icons.Default.Check, + null, + modifier = Modifier.size(18.dp) + ) + } + } + ) + } + } + } + } }, label = if (labelVisibilityMode != "unlabeled") { { Text(stringResource(destination.labelId)) } @@ -181,6 +252,9 @@ fun MainScreen( } destinations.forEachIndexed { index, destination -> val selected = pagerState.targetPage == index + var showGroupMenu by remember { mutableStateOf(false) } + val haptic = LocalHapticFeedback.current + NavigationBarItem( selected = selected, onClick = { @@ -189,7 +263,61 @@ fun MainScreen( } }, icon = { - NavigationIcon(destination, selected, uiState.upBooksCount) + Box { + NavigationIcon( + destination = destination, + selected = selected, + upBooksCount = uiState.upBooksCount, + modifier = if (destination == MainDestination.Bookshelf) { + Modifier.combinedClickable( + onClick = { + coroutineScope.launch { + pagerState.animateScrollToPage(index) + } + }, + onLongClick = { + haptic.performHapticFeedback( + HapticFeedbackType.LongPress + ) + showGroupMenu = true + } + ) + } else Modifier + ) + + if (destination == MainDestination.Bookshelf && showGroupMenu) { + RoundDropdownMenu( + expanded = showGroupMenu, + onDismissRequest = { showGroupMenu = false } + ) { dismiss -> + bookshelfUiState.groups.forEachIndexed { groupIndex, group -> + RoundDropdownMenuItem( + text = { Text(group.groupName) }, + onClick = { + coroutineScope.launch { + if (pagerState.currentPage != index) { + pagerState.scrollToPage(index) + } + bookshelfViewModel.changeGroup( + group.groupId + ) + dismiss() + } + }, + trailingIcon = { + if (bookshelfUiState.selectedGroupIndex == groupIndex) { + Icon( + Icons.Default.Check, + null, + modifier = Modifier.size(18.dp) + ) + } + } + ) + } + } + } + } }, colors = NavigationBarItemDefaults.colors( indicatorColor = GlassDefaults.glassColor( @@ -215,7 +343,8 @@ fun MainScreen( userScrollEnabled = true, beyondViewportPageCount = 3 ) { page -> - when (destinations[page]) { + val destination = destinations.getOrNull(page) ?: return@HorizontalPager + when (destination) { MainDestination.Bookshelf -> BookshelfScreen( onBookClick = { book -> context.startActivityForBook(book) @@ -248,14 +377,17 @@ fun MainScreen( private fun NavigationIcon( destination: MainDestination, selected: Boolean, - upBooksCount: Int + upBooksCount: Int, + modifier: Modifier = Modifier ) { val icon = if (selected) destination.selectedIcon else destination.icon - if (destination == MainDestination.Bookshelf && upBooksCount > 0) { - BadgedBox(badge = { Badge { Text(upBooksCount.toString()) } }) { + Box(modifier = modifier) { + if (destination == MainDestination.Bookshelf && upBooksCount > 0) { + BadgedBox(badge = { Badge { Text(upBooksCount.toString()) } }) { + Icon(icon, contentDescription = null) + } + } else { Icon(icon, contentDescription = null) } - } else { - Icon(icon, contentDescription = null) } }