代码优化

This commit is contained in:
HapeLee
2026-05-15 17:03:41 +08:00
parent 584ada1819
commit f411b7136e
8 changed files with 97 additions and 91 deletions
@@ -2,6 +2,7 @@ package io.legado.app.ui.main
import androidx.annotation.StringRes
import io.legado.app.R
import io.legado.app.ui.config.themeConfig.ThemeConfig
import kotlinx.collections.immutable.persistentListOf
sealed class MainDestination(
@@ -32,3 +33,11 @@ sealed class MainDestination(
val mainDestinations = persistentListOf<MainDestination>(Bookshelf, Explore, Rss, My)
}
}
val MainDestination.customIconPath: String
get() = when (this) {
MainDestination.Bookshelf -> ThemeConfig.navIconBookshelf
MainDestination.Explore -> ThemeConfig.navIconExplore
MainDestination.Rss -> ThemeConfig.navIconRss
MainDestination.My -> ThemeConfig.navIconMy
}
@@ -1,7 +1,6 @@
package io.legado.app.ui.main
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.activity.ComponentActivity
import androidx.appcompat.app.AppCompatActivity
@@ -24,7 +23,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
@@ -52,24 +50,22 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.graphics.Color
import androidx.core.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import com.kyant.backdrop.backdrops.layerBackdrop
import com.kyant.backdrop.backdrops.rememberLayerBackdrop
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.hazeSource
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import io.legado.app.R
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.main.bookshelf.BookshelfScreen
@@ -78,7 +74,6 @@ import io.legado.app.ui.main.explore.ExploreScreen
import io.legado.app.ui.main.my.MyScreen
import io.legado.app.ui.main.my.PrefClickEvent
import io.legado.app.ui.main.rss.RssScreen
import io.legado.app.ui.theme.regularHazeEffect
import io.legado.app.ui.widget.components.AppNavigationBar
import io.legado.app.ui.widget.components.AppNavigationBarItem
import io.legado.app.ui.widget.components.AppScaffold
@@ -95,6 +90,7 @@ import io.legado.app.utils.sendToClip
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.startActivityForBook
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.androidx.compose.koinViewModel
@@ -124,20 +120,21 @@ fun MainScreen(
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val mainUiState by viewModel.uiState.collectAsStateWithLifecycle()
val defaultHelpTitle = stringResource(R.string.help)
LaunchedEffect(viewModel, context) {
viewModel.effects.collect { effect ->
viewModel.effects.collectLatest { effect ->
when (effect) {
is MainEffect.OpenUrl -> {
context.startActivity(
Intent(Intent.ACTION_VIEW, Uri.parse(effect.url))
Intent(Intent.ACTION_VIEW, effect.url.toUri())
)
}
is MainEffect.CopyUrl -> context.sendToClip(effect.url)
is MainEffect.ShowMarkdown -> {
val activity = context as? AppCompatActivity ?: return@collect
val title = effect.title.ifBlank { context.getString(R.string.help) }
val activity = context as? AppCompatActivity ?: return@collectLatest
val title = effect.title.ifBlank { defaultHelpTitle }
val mdText = withContext(Dispatchers.IO) {
context.assets
.open("web/help/md/${effect.path}.md")
@@ -288,12 +285,7 @@ fun MainScreen(
}
},
label = if (labelVisibilityMode != "unlabeled") {
val hasCustomIcon = when (destination) {
MainDestination.Bookshelf -> ThemeConfig.navIconBookshelf.isNotEmpty()
MainDestination.Explore -> ThemeConfig.navIconExplore.isNotEmpty()
MainDestination.Rss -> ThemeConfig.navIconRss.isNotEmpty()
MainDestination.My -> ThemeConfig.navIconMy.isNotEmpty()
}
val hasCustomIcon = destination.customIconPath.isNotEmpty()
if (hasCustomIcon) null else {{ AppText(stringResource(destination.labelId)) }}
} else null
)
@@ -308,12 +300,7 @@ fun MainScreen(
AppNavigationBar() {
destinations.forEachIndexed { index, destination ->
val selected = pagerState.targetPage == index
val customIconPath = when (destination) {
MainDestination.Bookshelf -> ThemeConfig.navIconBookshelf
MainDestination.Explore -> ThemeConfig.navIconExplore
MainDestination.Rss -> ThemeConfig.navIconRss
MainDestination.My -> ThemeConfig.navIconMy
}
val customIconPath = destination.customIconPath
AppNavigationBarItem(
modifier = Modifier.semantics(mergeDescendants = true) {
contentDescription = "nav_${destination.route}"
@@ -442,22 +429,12 @@ fun MainScreen(
tabsCount = destinations.size,
isBlurEnabled = useLiquidGlass,
hasCustomIcons = destinations.any { dest ->
when (dest) {
MainDestination.Bookshelf -> ThemeConfig.navIconBookshelf.isNotEmpty()
MainDestination.Explore -> ThemeConfig.navIconExplore.isNotEmpty()
MainDestination.Rss -> ThemeConfig.navIconRss.isNotEmpty()
MainDestination.My -> ThemeConfig.navIconMy.isNotEmpty()
}
dest.customIconPath.isNotEmpty()
}
) {
destinations.forEachIndexed { index, destination ->
val selected = pagerState.targetPage == index
val hasCustomIcon = when (destination) {
MainDestination.Bookshelf -> ThemeConfig.navIconBookshelf.isNotEmpty()
MainDestination.Explore -> ThemeConfig.navIconExplore.isNotEmpty()
MainDestination.Rss -> ThemeConfig.navIconRss.isNotEmpty()
MainDestination.My -> ThemeConfig.navIconMy.isNotEmpty()
}
val hasCustomIcon = destination.customIconPath.isNotEmpty()
FloatingBottomBarItem(
onClick = {
coroutineScope.launch {
@@ -536,31 +513,13 @@ private fun NavigationIcon(
selected: Boolean,
modifier: Modifier = Modifier
) {
val customIconPath = when (destination) {
MainDestination.Bookshelf -> ThemeConfig.navIconBookshelf
MainDestination.Explore -> ThemeConfig.navIconExplore
MainDestination.Rss -> ThemeConfig.navIconRss
MainDestination.My -> ThemeConfig.navIconMy
}
val customIconPath = destination.customIconPath
if (customIconPath.isNotEmpty()) {
val context = LocalContext.current
val bitmap = remember(customIconPath) {
kotlin.runCatching {
android.graphics.BitmapFactory.decodeFile(customIconPath)
}.getOrNull()
}
if (bitmap != null) {
Image(
painter = remember(bitmap) {
BitmapPainter(bitmap.asImageBitmap())
},
contentDescription = null,
modifier = modifier.size(40.dp)
)
} else {
val icon = AppIcons.mainDestination(destination, selected)
AppIcon(icon, contentDescription = null, modifier = modifier)
}
AsyncImage(
model = customIconPath,
contentDescription = null,
modifier = modifier.size(40.dp)
)
} else {
val icon = AppIcons.mainDestination(destination, selected)
AppIcon(icon, contentDescription = null, modifier = modifier)
@@ -2,9 +2,10 @@ package io.legado.app.ui.main
import android.app.Application
import android.content.SharedPreferences
import androidx.compose.runtime.Stable
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.PreferKey
import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey
import io.legado.app.domain.usecase.AppStartupMaintenanceUseCase
import io.legado.app.domain.usecase.WebDavBackupUseCase
import io.legado.app.ui.config.themeConfig.ThemeConfig
@@ -13,13 +14,13 @@ import io.legado.app.utils.defaultSharedPreferences
import io.legado.app.utils.eventBus.FlowEventBus
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefString
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
class MainViewModel(
application: Application,
@@ -133,6 +134,7 @@ sealed interface MainEffect {
data object NavigateToReadRecord : MainEffect
}
@Stable
data class MainUiState(
val destinations: ImmutableList<MainDestination> = MainDestination.mainDestinations,
val defaultHomePage: String = "bookshelf",
@@ -1,6 +1,7 @@
package io.legado.app.ui.main.my
import android.app.Application
import androidx.compose.runtime.Stable
import androidx.lifecycle.viewModelScope
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.EventBus
@@ -12,6 +13,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@Stable
data class MyUiState(
val isWebServiceRun: Boolean = false,
val webServiceAddress: String = ""
@@ -5,7 +5,6 @@ import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -18,27 +17,22 @@ import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Login
import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Group
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.filled.Subscriptions
import androidx.compose.material.icons.filled.VerticalAlignTop
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -59,7 +53,6 @@ import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.widget.components.SourceIcon
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.ui.widget.components.divider.PillDivider
import io.legado.app.ui.widget.components.divider.PillHeaderDivider
import io.legado.app.ui.widget.components.list.ListScaffold
@@ -67,6 +60,7 @@ import io.legado.app.ui.widget.components.menuItem.MenuItemIcon
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.TopBarActionButton
import io.legado.app.utils.openUrl
import io.legado.app.utils.startActivity
import kotlinx.coroutines.flow.collectLatest
@@ -103,23 +97,35 @@ fun RssScreen(
is RssEffect.OpenExternalUrl -> {
currentContext.openUrl(effect.url)
}
is RssEffect.OpenSourceEdit -> {
currentContext.startActivity<RssSourceEditActivity> {
putExtra("sourceUrl", effect.sourceUrl)
}
}
is RssEffect.Login -> {
currentContext.startActivity<SourceLoginActivity> {
putExtra("type", "rssSource")
putExtra("key", effect.sourceUrl)
}
}
RssEffect.OpenRuleSub -> {
currentContext.startActivity<RuleSubActivity>()
}
RssEffect.OpenFavorites -> {
currentContext.startActivity<RssFavoritesActivity>()
}
RssEffect.OpenSourceManage -> {
currentContext.startActivity<RssSourceActivity>()
}
}
}
}
val edit: (RssSource) -> Unit = { rssSource ->
context.startActivity<RssSourceEditActivity> {
putExtra("sourceUrl", rssSource.sourceUrl)
}
}
val login: (RssSource) -> Unit = { rssSource ->
context.startActivity<SourceLoginActivity> {
putExtra("type", "rssSource")
putExtra("key", rssSource.sourceUrl)
}
}
ListScaffold(
title = stringResource(R.string.rss),
state = uiState,
@@ -130,12 +136,12 @@ fun RssScreen(
searchPlaceholder = stringResource(R.string.search_rss_source),
topBarActions = {
TopBarActionButton(
onClick = { context.startActivity<RuleSubActivity>() },
onClick = { viewModel.openRuleSub() },
imageVector = Icons.Default.Subscriptions,
contentDescription = stringResource(R.string.rule_subscription)
)
TopBarActionButton(
onClick = { context.startActivity<RssFavoritesActivity>() },
onClick = { viewModel.openFavorites() },
imageVector = Icons.Default.Star,
contentDescription = stringResource(R.string.favorite)
)
@@ -143,7 +149,7 @@ fun RssScreen(
dropDownMenuContent = { dismiss ->
RoundDropdownMenuItem(
onClick = {
context.startActivity<RssSourceActivity>()
viewModel.openSourceManage()
dismiss()
},
text = stringResource(R.string.rss_feed_management),
@@ -183,10 +189,10 @@ fun RssScreen(
source = source,
onClick = { viewModel.openSource(source) },
onTop = { viewModel.topSource(source) },
onEdit = { edit(source) },
onEdit = { viewModel.openSourceEdit(source) },
onDelete = { sourceToDeleteUrl = source.sourceUrl },
onDisable = { viewModel.disable(source) },
onLogin = { login(source) }
onLogin = { viewModel.login(source) }
)
}
}
@@ -1,5 +1,6 @@
package io.legado.app.ui.main.rss
import androidx.compose.runtime.Stable
import io.legado.app.data.entities.RssSource
import io.legado.app.ui.widget.components.list.ListUiState
import kotlinx.collections.immutable.ImmutableList
@@ -7,6 +8,7 @@ import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentSetOf
@Stable
data class RssUiState(
override val items: ImmutableList<RssSource> = persistentListOf(),
override val selectedIds: ImmutableSet<String> = persistentSetOf(),
@@ -116,6 +116,26 @@ class RssViewModel(
}
}
fun openSourceEdit(rssSource: RssSource) {
_effects.tryEmit(RssEffect.OpenSourceEdit(rssSource.sourceUrl))
}
fun login(rssSource: RssSource) {
_effects.tryEmit(RssEffect.Login(rssSource.sourceUrl))
}
fun openRuleSub() {
_effects.tryEmit(RssEffect.OpenRuleSub)
}
fun openFavorites() {
_effects.tryEmit(RssEffect.OpenFavorites)
}
fun openSourceManage() {
_effects.tryEmit(RssEffect.OpenSourceManage)
}
fun openSource(rssSource: RssSource) {
if (!rssSource.singleUrl) {
_effects.tryEmit(RssEffect.OpenSort(rssSource.sourceUrl, null, null))
@@ -186,4 +206,9 @@ sealed interface RssEffect {
) : RssEffect
data class OpenExternalUrl(val url: String) : RssEffect
data class OpenSourceEdit(val sourceUrl: String) : RssEffect
data class Login(val sourceUrl: String) : RssEffect
data object OpenRuleSub : RssEffect
data object OpenFavorites : RssEffect
data object OpenSourceManage : RssEffect
}