[优化] 修复和优化了一些问题

This commit is contained in:
HapeLee committed 2026-04-09 01:21:59 +08:00
1 parent 68db8b914e
commit ce4598fa7f
13 files changed
+85 -105

No files matched your search

@@ -38,6 +38,5 @@ object EventBus {
const val REFRESH_BOOK_CONTENT = "refreshBookContent" const val REFRESH_BOOK_CONTENT = "refreshBookContent"
const val UP_TOC = "upToc" const val UP_TOC = "upToc"
const val UP_BOOKSHELF_COUNT = "upBookshelfCount"
const val UP_ALL_BOOK_TOC = "upAllBookToc" const val UP_ALL_BOOK_TOC = "upAllBookToc"
} }
@@ -106,7 +106,7 @@ val appModule = module {
ReplaceEditViewModel( ReplaceEditViewModel(
app = get(), app = get(),
replaceRuleDao = get(), replaceRuleDao = get(),
savedStateHandle = get() route = route
) )
} }
@@ -4,7 +4,6 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -31,15 +30,12 @@ import androidx.compose.material.icons.filled.Merge
import androidx.compose.material.icons.filled.Schedule import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Timeline import androidx.compose.material.icons.filled.Timeline
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -52,7 +48,6 @@ import androidx.compose.runtime.rememberCoroutineScope
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.draw.clip
import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
@@ -80,7 +75,6 @@ import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.EmptyMessageView import io.legado.app.ui.widget.components.EmptyMessageView
import io.legado.app.ui.widget.components.SearchBarSection import io.legado.app.ui.widget.components.SearchBarSection
import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.button.AlertButton
import io.legado.app.ui.widget.components.button.AppIconButton import io.legado.app.ui.widget.components.button.AppIconButton
import io.legado.app.ui.widget.components.button.TopBarNavigationButton import io.legado.app.ui.widget.components.button.TopBarNavigationButton
import io.legado.app.ui.widget.components.card.GlassCard import io.legado.app.ui.widget.components.card.GlassCard
@@ -100,7 +94,6 @@ import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.swipe.SwipeAction import io.legado.app.ui.widget.components.swipe.SwipeAction
import io.legado.app.ui.widget.components.swipe.SwipeActionContainer import io.legado.app.ui.widget.components.swipe.SwipeActionContainer
import io.legado.app.ui.widget.components.text.AnimatedTextLine
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
@@ -152,15 +145,13 @@ fun ReadRecordScreen(
Column { Column {
GlassMediumFlexibleTopAppBar( GlassMediumFlexibleTopAppBar(
title = "阅读记录", title = "阅读记录",
subtitle = { subtitle = run {
val subTitle = when (displayMode) { val subTitle = when (displayMode) {
DisplayMode.AGGREGATE -> "汇总视图" DisplayMode.AGGREGATE -> "汇总视图"
DisplayMode.TIMELINE -> "时间线视图" DisplayMode.TIMELINE -> "时间线视图"
DisplayMode.LATEST -> "最后阅读" DisplayMode.LATEST -> "最后阅读"
} }
AnimatedTextLine( subTitle
text = subTitle
)
}, },
navigationIcon = { navigationIcon = {
TopBarNavigationButton(onClick = onBackClick) TopBarNavigationButton(onClick = onBackClick)
@@ -157,25 +157,37 @@ fun TocScreen(
val useReplace = viewModel.useReplace val useReplace = viewModel.useReplace
val showWordCount = viewModel.showWordCount val showWordCount = viewModel.showWordCount
val subtitle = remember( val topBarTitle = remember(
pagerState.currentPage, pagerState.currentPage,
book?.name,
book?.durChapterTitle, book?.durChapterTitle,
) {
when (pagerState.currentPage) {
0 -> {
book?.durChapterTitle?.takeIf { it.isNotBlank() } ?: (book?.name ?: "")
}
1 -> "书签管理"
else -> book?.name ?: ""
}
}
val topBarSubtitle = remember(
pagerState.currentPage,
book?.durChapterIndex, book?.durChapterIndex,
book?.totalChapterNum book?.totalChapterNum
) { ) {
when (pagerState.currentPage) { when (pagerState.currentPage) {
0 -> { 0 -> {
val durTitle = book?.durChapterTitle ?: "" val durIndex = (book?.durChapterIndex ?: -1) + 1
val durIndex = (book?.durChapterIndex ?: 0) + 1
val totalNum = book?.totalChapterNum ?: 0 val totalNum = book?.totalChapterNum ?: 0
if (totalNum > 0) { if (durIndex > 0 && totalNum > 0) {
"$durTitle · $durIndex / $totalNum 章" "$durIndex / $totalNum"
} else { } else {
durTitle null
} }
} }
1 -> "书签管理"
else -> null else -> null
} }
} }
@@ -301,8 +313,8 @@ fun TocScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
DynamicTopAppBar( DynamicTopAppBar(
title = book?.name ?: "", title = topBarTitle,
subtitle = subtitle, subtitle = topBarSubtitle,
state = state, state = state,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
onBackClick = onBackClick, onBackClick = onBackClick,
@@ -25,15 +25,12 @@ import androidx.compose.material.icons.automirrored.filled.MenuOpen
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox
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.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.Text
import androidx.compose.material3.WideNavigationRail import androidx.compose.material3.WideNavigationRail
import androidx.compose.material3.WideNavigationRailItem import androidx.compose.material3.WideNavigationRailItem
import androidx.compose.material3.WideNavigationRailValue import androidx.compose.material3.WideNavigationRailValue
@@ -54,7 +51,6 @@ import androidx.compose.ui.platform.LocalHapticFeedback
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
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.kyant.backdrop.backdrops.layerBackdrop import com.kyant.backdrop.backdrops.layerBackdrop
import com.kyant.backdrop.backdrops.rememberLayerBackdrop import com.kyant.backdrop.backdrops.rememberLayerBackdrop
import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeState
@@ -68,7 +64,6 @@ import io.legado.app.ui.main.bookshelf.BookshelfViewModel
import io.legado.app.ui.main.explore.ExploreScreen import io.legado.app.ui.main.explore.ExploreScreen
import io.legado.app.ui.main.my.MyScreen import io.legado.app.ui.main.my.MyScreen
import io.legado.app.ui.main.rss.RssScreen import io.legado.app.ui.main.rss.RssScreen
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.regularHazeEffect import io.legado.app.ui.theme.regularHazeEffect
import io.legado.app.ui.widget.components.AppNavigationBar import io.legado.app.ui.widget.components.AppNavigationBar
import io.legado.app.ui.widget.components.AppNavigationBarItem import io.legado.app.ui.widget.components.AppNavigationBarItem
@@ -97,7 +92,6 @@ fun MainScreen(
onOpenSettings: () -> Unit onOpenSettings: () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
val uiState by viewModel.state.collectAsStateWithLifecycle()
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val bookshelfViewModel: BookshelfViewModel = koinViewModel() val bookshelfViewModel: BookshelfViewModel = koinViewModel()
@@ -203,7 +197,6 @@ fun MainScreen(
NavigationIcon( NavigationIcon(
destination = destination, destination = destination,
selected = selected, selected = selected,
upBooksCount = uiState.upBooksCount,
modifier = if (destination == MainDestination.Bookshelf) { modifier = if (destination == MainDestination.Bookshelf) {
Modifier.combinedClickable( Modifier.combinedClickable(
onClick = { onClick = {
@@ -280,7 +273,7 @@ fun MainScreen(
.asPaddingValues() .asPaddingValues()
.calculateBottomPadding() .calculateBottomPadding()
), ),
selectedIndex = { pagerState.currentPage }, selectedIndex = { pagerState.targetPage },
onSelected = { index -> onSelected = { index ->
coroutineScope.launch { coroutineScope.launch {
pagerState.animateScrollToPage(index) pagerState.animateScrollToPage(index)
@@ -291,7 +284,7 @@ fun MainScreen(
isBlurEnabled = useLiquidGlass isBlurEnabled = useLiquidGlass
) { ) {
destinations.forEachIndexed { index, destination -> destinations.forEachIndexed { index, destination ->
val selected = pagerState.currentPage == index val selected = pagerState.targetPage == index
FloatingBottomBarItem( FloatingBottomBarItem(
onClick = { onClick = {
coroutineScope.launch { coroutineScope.launch {
@@ -302,18 +295,12 @@ fun MainScreen(
) { ) {
NavigationIcon( NavigationIcon(
destination = destination, destination = destination,
selected = selected, selected = selected
upBooksCount = uiState.upBooksCount
) )
if (showLabel && (alwaysShowLabel || selected)) { if (showLabel && (alwaysShowLabel || selected)) {
Text( AppText(
text = stringResource(destination.labelId), text = stringResource(destination.labelId),
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelSmall,
color = if (selected) {
LegadoTheme.colorScheme.primary
} else {
LegadoTheme.colorScheme.onSurface
},
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
@@ -340,8 +327,7 @@ fun MainScreen(
m3Icon = { m3Icon = {
NavigationIcon( NavigationIcon(
destination = destination, destination = destination,
selected = selected, selected = selected
upBooksCount = uiState.upBooksCount
) )
}, },
m3IndicatorColor = GlassDefaults.glassColor( m3IndicatorColor = GlassDefaults.glassColor(
@@ -410,17 +396,8 @@ fun MainScreen(
private fun NavigationIcon( private fun NavigationIcon(
destination: MainDestination, destination: MainDestination,
selected: Boolean, selected: Boolean,
upBooksCount: Int,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val icon = AppIcons.mainDestination(destination, selected) val icon = AppIcons.mainDestination(destination, selected)
Box(modifier = modifier) { AppIcon(icon, contentDescription = null, modifier = modifier)
if (destination == MainDestination.Bookshelf && upBooksCount > 0) {
BadgedBox(badge = { Badge { Text(upBooksCount.toString()) } }) {
AppIcon(icon, contentDescription = null)
}
} else {
AppIcon(icon, contentDescription = null)
}
}
} }
@@ -3,7 +3,6 @@ package io.legado.app.ui.main
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.lifecycle.viewModelScope
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.appDb import io.legado.app.data.appDb
@@ -12,31 +11,10 @@ import io.legado.app.help.DefaultData
import io.legado.app.ui.main.my.PrefClickEvent import io.legado.app.ui.main.my.PrefClickEvent
import io.legado.app.utils.eventBus.FlowEventBus import io.legado.app.utils.eventBus.FlowEventBus
import io.legado.app.utils.sendToClip import io.legado.app.utils.sendToClip
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
data class MainUiState(
val upBooksCount: Int = 0
)
class MainViewModel(application: Application) : BaseViewModel(application) { class MainViewModel(application: Application) : BaseViewModel(application) {
private val upBooksCountFlow = MutableStateFlow(0)
val state: StateFlow<MainUiState> = upBooksCountFlow
.map { count -> MainUiState(count) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), MainUiState())
init { init {
viewModelScope.launch {
FlowEventBus.with<Int>(EventBus.UP_BOOKSHELF_COUNT).collect {
upBooksCountFlow.value = it
}
}
deleteNotShelfBook() deleteNotShelfBook()
} }
@@ -198,7 +198,7 @@ fun BookshelfScreen(
val bookGroupStyle = BookshelfConfig.bookGroupStyle val bookGroupStyle = BookshelfConfig.bookGroupStyle
// 控制是否处于“文件夹列表”根视图,还是“文件夹内部”书籍视图 // 控制是否处于“文件夹列表”根视图,还是“文件夹内部”书籍视图
var isInFolderRoot by remember(bookGroupStyle) { mutableStateOf(bookGroupStyle == 2) } var isInFolderRoot by remember(bookGroupStyle) { mutableStateOf(bookGroupStyle == 2) }
val title = when { val baseTitle = when {
bookGroupStyle == 1 -> { bookGroupStyle == 1 -> {
uiState.groups.getOrNull(pagerState.currentPage)?.groupName uiState.groups.getOrNull(pagerState.currentPage)?.groupName
?: stringResource(R.string.bookshelf) ?: stringResource(R.string.bookshelf)
@@ -212,6 +212,11 @@ fun BookshelfScreen(
else -> stringResource(R.string.bookshelf) else -> stringResource(R.string.bookshelf)
} }
val title = if (uiState.upBooksCount > 0) {
"$baseTitle (${uiState.upBooksCount})"
} else {
baseTitle
}
if (bookGroupStyle == 2 && !isInFolderRoot) { if (bookGroupStyle == 2 && !isInFolderRoot) {
BackHandler { BackHandler {
@@ -14,5 +14,6 @@ data class BookshelfUiState(
val groupBookCounts: Map<Long, Int> = emptyMap(), val groupBookCounts: Map<Long, Int> = emptyMap(),
val selectedGroupIndex: Int = 0, val selectedGroupIndex: Int = 0,
val loadingText: String? = null, val loadingText: String? = null,
val upBooksCount: Int = 0,
val updatingBooks: Set<String> = emptySet() val updatingBooks: Set<String> = emptySet()
) : ListUiState<BookShelfItem> ) : ListUiState<BookShelfItem>
@@ -107,6 +107,7 @@ class BookshelfViewModel(
private val waitUpTocBooks = LinkedList<String>() private val waitUpTocBooks = LinkedList<String>()
private val onUpTocBooks = ConcurrentHashMap.newKeySet<String>() private val onUpTocBooks = ConcurrentHashMap.newKeySet<String>()
private val updatingBooksFlow = MutableStateFlow<Set<String>>(emptySet()) private val updatingBooksFlow = MutableStateFlow<Set<String>>(emptySet())
private val upBooksCountFlow = MutableStateFlow(0)
private var upTocJob: Job? = null private var upTocJob: Job? = null
private var cacheBookJob: Job? = null private var cacheBookJob: Job? = null
private val eventListenerSource = ConcurrentHashMap<BookSource, Boolean>() private val eventListenerSource = ConcurrentHashMap<BookSource, Boolean>()
@@ -210,16 +211,18 @@ class BookshelfViewModel(
groupIdFlow, groupIdFlow,
searchKeyFlow, searchKeyFlow,
loadingTextFlow, loadingTextFlow,
updatingBooksFlow updatingBooksFlow,
) { groupId, searchKey, loadingText, updatingBooks -> upBooksCountFlow
InternalState(groupId, searchKey, loadingText, updatingBooks) ) { groupId, searchKey, loadingText, updatingBooks, upBooksCount ->
InternalState(groupId, searchKey, loadingText, updatingBooks, upBooksCount)
} }
data class InternalState( data class InternalState(
val groupId: Long, val groupId: Long,
val searchKey: String, val searchKey: String,
val loadingText: String?, val loadingText: String?,
val updatingBooks: Set<String> val updatingBooks: Set<String>,
val upBooksCount: Int
) )
val uiState: StateFlow<BookshelfUiState> = combine( val uiState: StateFlow<BookshelfUiState> = combine(
@@ -250,6 +253,7 @@ class BookshelfViewModel(
isSearch = internal.searchKey.isNotEmpty(), isSearch = internal.searchKey.isNotEmpty(),
isLoading = internal.loadingText != null, isLoading = internal.loadingText != null,
loadingText = internal.loadingText, loadingText = internal.loadingText,
upBooksCount = internal.upBooksCount,
updatingBooks = internal.updatingBooks updatingBooks = internal.updatingBooks
) )
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), BookshelfUiState()) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), BookshelfUiState())
@@ -276,6 +280,9 @@ class BookshelfViewModel(
viewModelScope.launch { viewModelScope.launch {
snapshotFlow { BookshelfConfig.bookGroupStyle }.collect { refresh() } snapshotFlow { BookshelfConfig.bookGroupStyle }.collect { refresh() }
} }
viewModelScope.launch {
snapshotFlow { BookshelfConfig.showWaitUpCount }.collect { postUpBooksCount() }
}
if (BookshelfConfig.autoRefreshBook) { if (BookshelfConfig.autoRefreshBook) {
upAllBookToc() upAllBookToc()
@@ -382,6 +389,7 @@ class BookshelfViewModel(
waitUpTocBooks.add(book.bookUrl) waitUpTocBooks.add(book.bookUrl)
} }
} }
postUpBooksCount()
if (upTocJob == null) { if (upTocJob == null) {
startUpTocJob() startUpTocJob()
} }
@@ -480,7 +488,7 @@ class BookshelfViewModel(
private fun postUpBooksCount() { private fun postUpBooksCount() {
val count = val count =
if (BookshelfConfig.showWaitUpCount) waitUpTocBooks.size + onUpTocBooks.size else 0 if (BookshelfConfig.showWaitUpCount) waitUpTocBooks.size + onUpTocBooks.size else 0
FlowEventBus.post(EventBus.UP_BOOKSHELF_COUNT, count) upBooksCountFlow.value = count
} }
private fun addDownload(source: BookSource, book: Book) { private fun addDownload(source: BookSource, book: Book) {
@@ -1,10 +1,8 @@
package io.legado.app.ui.replace.edit package io.legado.app.ui.replace.edit
import android.app.Application import android.app.Application
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.toRoute
import io.legado.app.data.dao.ReplaceRuleDao import io.legado.app.data.dao.ReplaceRuleDao
import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.ReplaceRule
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
@@ -41,11 +39,9 @@ data class ReplaceEditUiState(
class ReplaceEditViewModel( class ReplaceEditViewModel(
private val app: Application, private val app: Application,
private val replaceRuleDao: ReplaceRuleDao, private val replaceRuleDao: ReplaceRuleDao,
private val savedStateHandle: SavedStateHandle private val route: ReplaceEditRoute
) : ViewModel() { ) : ViewModel() {
private val route = savedStateHandle.toRoute<ReplaceEditRoute>()
private val _uiState = MutableStateFlow(ReplaceEditUiState()) private val _uiState = MutableStateFlow(ReplaceEditUiState())
val uiState = _uiState.asStateFlow() val uiState = _uiState.asStateFlow()
@@ -59,7 +55,6 @@ class ReplaceEditViewModel(
private fun initData() { private fun initData() {
viewModelScope.launch { viewModelScope.launch {
val id = route.id val id = route.id
if (id > 0) { if (id > 0) {
@@ -67,7 +67,6 @@ import io.legado.app.ui.animation.DampedDragAnimation
import io.legado.app.ui.animation.InteractiveHighlight import io.legado.app.ui.animation.InteractiveHighlight
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.sign import kotlin.math.sign
@@ -143,7 +142,7 @@ fun FloatingBottomBar(
} }
} }
var currentIndex by remember(selectedIndex) { mutableIntStateOf(selectedIndex()) } var currentIndex by remember { mutableIntStateOf(selectedIndex()) }
class DampedDragAnimationHolder { class DampedDragAnimationHolder {
var instance: DampedDragAnimation? = null var instance: DampedDragAnimation? = null
@@ -179,6 +178,9 @@ fun FloatingBottomBar(
val targetIndex = targetValue.fastRoundToInt().fastCoerceIn(0, tabsCount - 1) val targetIndex = targetValue.fastRoundToInt().fastCoerceIn(0, tabsCount - 1)
currentIndex = targetIndex currentIndex = targetIndex
animateToValue(targetIndex.toFloat()) animateToValue(targetIndex.toFloat())
if (targetIndex != selectedIndex()) {
onSelected(targetIndex)
}
animationScope.launch { animationScope.launch {
offsetAnimation.animateTo(0f, spring(1f, 300f, 0.5f)) offsetAnimation.animateTo(0f, spring(1f, 300f, 0.5f))
} }
@@ -197,13 +199,10 @@ fun FloatingBottomBar(
).also { holder.instance = it } ).also { holder.instance = it }
} }
LaunchedEffect(selectedIndex) { LaunchedEffect(selectedIndex, dampedDragAnimation) {
snapshotFlow { selectedIndex() }.collectLatest { currentIndex = it } snapshotFlow { selectedIndex() }.collectLatest { index ->
} currentIndex = index
LaunchedEffect(dampedDragAnimation) {
snapshotFlow { currentIndex }.drop(1).collectLatest { index ->
dampedDragAnimation.animateToValue(index.toFloat()) dampedDragAnimation.animateToValue(index.toFloat())
onSelected(index)
} }
} }
@@ -27,7 +27,6 @@ import io.legado.app.ui.widget.components.button.TopBarNavigationButton
import io.legado.app.ui.widget.components.icon.AppIcons import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.list.ListUiState import io.legado.app.ui.widget.components.list.ListUiState
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.text.AnimatedTextLine
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -62,9 +61,7 @@ fun <T> DynamicTopAppBar(
else -> title else -> title
}, },
useCharMode = isSelecting || state.isLoading, useCharMode = isSelecting || state.isLoading,
subtitle = subtitle?.let { subtitle = subtitle,
{ AnimatedTextLine(text = it) }
},
navigationIcon = { navigationIcon = {
if (isSelecting || onBackClick != null) { if (isSelecting || onBackClick != null) {
TopBarNavigationButton( TopBarNavigationButton(
@@ -23,6 +23,7 @@ import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.responsiveHazeEffect import io.legado.app.ui.theme.responsiveHazeEffect
import io.legado.app.ui.widget.components.GlassDefaults import io.legado.app.ui.widget.components.GlassDefaults
import io.legado.app.ui.widget.components.text.AdaptiveAnimatedText import io.legado.app.ui.widget.components.text.AdaptiveAnimatedText
import io.legado.app.ui.widget.components.text.AnimatedTextLine
import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior import top.yukonga.miuix.kmp.basic.MiuixScrollBehavior
import top.yukonga.miuix.kmp.theme.MiuixTheme import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.basic.TopAppBar as MiuixTopAppBar import top.yukonga.miuix.kmp.basic.TopAppBar as MiuixTopAppBar
@@ -33,7 +34,7 @@ fun GlassMediumFlexibleTopAppBar(
title: String, title: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
useCharMode: Boolean = false, useCharMode: Boolean = false,
subtitle: (@Composable () -> Unit)? = null, subtitle: String? = null,
scrollBehavior: GlassTopAppBarScrollBehavior? = null, scrollBehavior: GlassTopAppBarScrollBehavior? = null,
navigationIcon: @Composable () -> Unit = {}, navigationIcon: @Composable () -> Unit = {},
actions: @Composable RowScope.() -> Unit = {}, actions: @Composable RowScope.() -> Unit = {},
@@ -73,6 +74,7 @@ fun GlassMediumFlexibleTopAppBar(
containerColor = Color.Transparent, containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent scrolledContainerColor = Color.Transparent
) )
val subtitleText = subtitle?.takeIf { it.isNotBlank() }
Column( Column(
modifier = finalModifier modifier = finalModifier
@@ -82,6 +84,7 @@ fun GlassMediumFlexibleTopAppBar(
MiuixTopAppBar( MiuixTopAppBar(
modifier = Modifier, modifier = Modifier,
title = title, title = title,
subtitle = subtitleText.orEmpty(),
navigationIcon = navigationIcon, navigationIcon = navigationIcon,
actions = actions, actions = actions,
color = Color.Transparent, color = Color.Transparent,
@@ -101,7 +104,11 @@ fun GlassMediumFlexibleTopAppBar(
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
}, },
subtitle = subtitle, subtitle = subtitleText?.let { text ->
{
AnimatedTextLine(text = text)
}
},
navigationIcon = navigationIcon, navigationIcon = navigationIcon,
actions = actions, actions = actions,
scrollBehavior = (scrollBehavior as? M3GlassScrollBehavior)?.m3Behavior, scrollBehavior = (scrollBehavior as? M3GlassScrollBehavior)?.m3Behavior,
@@ -111,12 +118,23 @@ fun GlassMediumFlexibleTopAppBar(
TopAppBar( TopAppBar(
modifier = Modifier, modifier = Modifier,
title = { title = {
AdaptiveAnimatedText( Column {
text = title, AdaptiveAnimatedText(
useCharMode = useCharMode, text = title,
maxLines = 1, useCharMode = useCharMode,
overflow = TextOverflow.Ellipsis maxLines = 1,
) overflow = TextOverflow.Ellipsis
)
subtitleText?.let { text ->
AnimatedTextLine(
text = text,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}, },
navigationIcon = navigationIcon, navigationIcon = navigationIcon,
actions = actions, actions = actions,