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

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