[优化] 优化界面

This commit is contained in:
HapeLee
2026-04-14 00:50:56 +08:00
parent 6a354acd29
commit 9410e9e0b5
13 changed files with 114 additions and 56 deletions
@@ -134,6 +134,7 @@ object PreferKey {
const val doublePageHorizontal = "doubleHorizontalPage" const val doublePageHorizontal = "doubleHorizontalPage"
const val readUrlOpenInBrowser = "readUrlInBrowser" const val readUrlOpenInBrowser = "readUrlInBrowser"
const val defaultBookTreeUri = "defaultBookTreeUri" const val defaultBookTreeUri = "defaultBookTreeUri"
const val importBookPath = "importBookPath"
const val checkSource = "checkSource" const val checkSource = "checkSource"
const val uploadRule = "uploadRule" const val uploadRule = "uploadRule"
const val tocUiUseReplace = "tocUiUseReplace" const val tocUiUseReplace = "tocUiUseReplace"
@@ -15,6 +15,7 @@ import io.legado.app.data.repository.ReadRecordRepository
import io.legado.app.data.repository.RemoteBookRepository import io.legado.app.data.repository.RemoteBookRepository
import io.legado.app.data.repository.SearchContentRepository import io.legado.app.data.repository.SearchContentRepository
import io.legado.app.data.repository.UploadRepository import io.legado.app.data.repository.UploadRepository
import io.legado.app.domain.usecase.ExploreKindUiUseCase
import io.legado.app.help.coil.CoverFetcher import io.legado.app.help.coil.CoverFetcher
import io.legado.app.help.coil.CoverInterceptor import io.legado.app.help.coil.CoverInterceptor
import io.legado.app.help.http.okHttpClient import io.legado.app.help.http.okHttpClient
@@ -64,6 +65,7 @@ val appModule = module {
singleOf(::BookGroupRepository) singleOf(::BookGroupRepository)
singleOf(::SearchContentRepository) singleOf(::SearchContentRepository)
singleOf(::RemoteBookRepository) singleOf(::RemoteBookRepository)
singleOf(::ExploreKindUiUseCase)
single<UploadRepository> { DirectLinkUploadRepository() } single<UploadRepository> { DirectLinkUploadRepository() }
single<ExploreRepository> { ExploreRepositoryImpl(get()) } single<ExploreRepository> { ExploreRepositoryImpl(get()) }
@@ -1,6 +1,7 @@
package io.legado.app.help.source package io.legado.app.help.source
import com.script.rhino.runScriptWithContext import com.script.rhino.runScriptWithContext
import androidx.collection.LruCache
import io.legado.app.constant.BookSourceType import io.legado.app.constant.BookSourceType
import io.legado.app.constant.BookType import io.legado.app.constant.BookType
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
@@ -8,6 +9,7 @@ import io.legado.app.data.entities.BookSourcePart
import io.legado.app.data.entities.rule.ExploreKind import io.legado.app.data.entities.rule.ExploreKind
import io.legado.app.utils.ACache import io.legado.app.utils.ACache
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.InfoMap
import io.legado.app.utils.MD5Utils import io.legado.app.utils.MD5Utils
import io.legado.app.utils.fromJsonArray import io.legado.app.utils.fromJsonArray
import io.legado.app.utils.isJsonArray import io.legado.app.utils.isJsonArray
@@ -25,6 +27,15 @@ import java.util.concurrent.ConcurrentHashMap
private val mutexMap by lazy { hashMapOf<String, Mutex>() } private val mutexMap by lazy { hashMapOf<String, Mutex>() }
private val exploreKindsMap by lazy { ConcurrentHashMap<String, List<ExploreKind>>() } private val exploreKindsMap by lazy { ConcurrentHashMap<String, List<ExploreKind>>() }
private val aCache by lazy { ACache.get("explore") } private val aCache by lazy { ACache.get("explore") }
private val exploreInfoMapList by lazy { LruCache<String, InfoMap>(99) }
fun getExploreInfoMap(sourceUrl: String): InfoMap {
return synchronized(exploreInfoMapList) {
exploreInfoMapList[sourceUrl] ?: InfoMap(sourceUrl).also {
exploreInfoMapList.put(sourceUrl, it)
}
}
}
private fun BookSource.getExploreKindsKey(): String { private fun BookSource.getExploreKindsKey(): String {
return MD5Utils.md5Encode(bookSourceUrl + exploreUrl) return MD5Utils.md5Encode(bookSourceUrl + exploreUrl)
@@ -57,13 +68,16 @@ suspend fun BookSource.exploreKinds(): List<ExploreKind> {
) { ) {
ruleStr = aCache.getAsString(exploreKindsKey) ruleStr = aCache.getAsString(exploreKindsKey)
if (ruleStr.isNullOrBlank()) { if (ruleStr.isNullOrBlank()) {
val exploreInfoMap = getExploreInfoMap(bookSourceUrl)
val jsStr = if (exploreUrl.startsWith("@")) { val jsStr = if (exploreUrl.startsWith("@")) {
exploreUrl.substring(4) exploreUrl.substring(4)
} else { } else {
exploreUrl.substring(4, exploreUrl.lastIndexOf("<")) exploreUrl.substring(4, exploreUrl.lastIndexOf("<"))
} }
ruleStr = runScriptWithContext { ruleStr = runScriptWithContext {
evalJS(jsStr).toString().trim() evalJS(jsStr) {
put("infoMap", exploreInfoMap)
}.toString().trim()
} }
aCache.put(exploreKindsKey, ruleStr) aCache.put(exploreKindsKey, ruleStr)
} }
@@ -304,6 +304,7 @@ fun RemoteBookScreen(
) )
}, },
selectionActions = SelectionActions( selectionActions = SelectionActions(
onClearSelection = { viewModel.clearSelection() },
onSelectAll = { viewModel.dispatch(RemoteBookIntent.SelectAll) }, onSelectAll = { viewModel.dispatch(RemoteBookIntent.SelectAll) },
onSelectInvert = { viewModel.dispatch(RemoteBookIntent.SelectInvert) }, onSelectInvert = { viewModel.dispatch(RemoteBookIntent.SelectInvert) },
primaryAction = ActionItem( primaryAction = ActionItem(
@@ -845,11 +845,14 @@ private fun BookInfoSummary(
) { ) {
AppText( AppText(
text = stringResource(R.string.read_chapter_total, book.totalChapterNum), text = stringResource(R.string.read_chapter_total, book.totalChapterNum),
style = LegadoTheme.typography.bodyMedium, style = LegadoTheme.typography.labelMedium,
color = LegadoTheme.colorScheme.primary, color = LegadoTheme.colorScheme.primary,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
) )
AppText(text = "|", color = LegadoTheme.colorScheme.secondary) AppText(
text = "|",
color = LegadoTheme.colorScheme.secondary
)
AppText( AppText(
text = if (book.durChapterIndex + 1 == book.totalChapterNum && book.totalChapterNum > 0) "已读完" else stringResource(R.string.read_chapter_index, book.durChapterIndex + 1), text = if (book.durChapterIndex + 1 == book.totalChapterNum && book.totalChapterNum > 0) "已读完" else stringResource(R.string.read_chapter_index, book.durChapterIndex + 1),
style = LegadoTheme.typography.labelMedium, style = LegadoTheme.typography.labelMedium,
@@ -13,8 +13,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import io.legado.app.ui.config.themeConfig.ThemeConfig import io.legado.app.ui.config.themeConfig.ThemeConfig
import top.yukonga.miuix.kmp.theme.MiuixTheme import top.yukonga.miuix.kmp.theme.MiuixTheme
import top.yukonga.miuix.kmp.theme.ThemeController import top.yukonga.miuix.kmp.theme.ThemeController
@@ -132,37 +130,7 @@ fun AppTheme(
MiuixTheme(controller = controller) { MiuixTheme(controller = controller) {
val miuixStyles = MiuixTheme.textStyles val miuixStyles = MiuixTheme.textStyles
val legadoTypography = remember(miuixStyles) { val legadoTypography = remember(miuixStyles) {
LegadoTypography( miuixStylesToM3Typography(miuixStyles).toLegadoTypography()
headlineLarge = miuixStyles.title1,
headlineLargeEmphasized = miuixStyles.title1.emphasized(),
headlineMedium = miuixStyles.title2,
headlineMediumEmphasized = miuixStyles.title2.emphasized(),
headlineSmall = miuixStyles.title3,
headlineSmallEmphasized = miuixStyles.title3.emphasized(),
titleLarge = miuixStyles.headline1,
titleLargeEmphasized = miuixStyles.headline1.emphasized(),
titleMedium = miuixStyles.headline2,
titleMediumEmphasized = miuixStyles.headline2.emphasized(),
titleSmall = miuixStyles.subtitle,
titleSmallEmphasized = miuixStyles.subtitle.emphasized(),
bodyLarge = miuixStyles.paragraph,
bodyLargeEmphasized = miuixStyles.paragraph.emphasized(),
bodyMedium = miuixStyles.body1,
bodyMediumEmphasized = miuixStyles.body1.emphasized(),
bodySmall = miuixStyles.body2,
bodySmallEmphasized = miuixStyles.body2.emphasized(),
labelLarge = miuixStyles.button,
labelLargeEmphasized = miuixStyles.button.emphasized(),
labelMedium = miuixStyles.footnote1,
labelMediumEmphasized = miuixStyles.footnote1.emphasized(),
labelSmall = miuixStyles.footnote2,
labelSmallEmphasized = miuixStyles.footnote2.emphasized()
)
} }
val miuixColorScheme = MiuixTheme.colorScheme val miuixColorScheme = MiuixTheme.colorScheme
@@ -290,7 +258,3 @@ private fun Typography.toLegadoTypography(): LegadoTypography {
labelSmallEmphasized = labelSmallEmphasized labelSmallEmphasized = labelSmallEmphasized
) )
} }
private fun TextStyle.emphasized(): TextStyle {
return copy(fontWeight = FontWeight.Medium)
}
@@ -3,6 +3,7 @@ package io.legado.app.ui.theme
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import top.yukonga.miuix.kmp.theme.TextStyles import top.yukonga.miuix.kmp.theme.TextStyles
@@ -17,28 +18,34 @@ fun miuixStylesToM3Typography(miuixStyles: TextStyles): Typography {
displayMedium = miuixStyles.title2, // 24.sp displayMedium = miuixStyles.title2, // 24.sp
displaySmall = miuixStyles.title3, // 20.sp displaySmall = miuixStyles.title3, // 20.sp
headlineLarge = miuixStyles.title2, // 24.sp headlineLarge = miuixStyles.title1, // 32.sp
headlineMedium = miuixStyles.title3, // 20.sp headlineMedium = miuixStyles.title2, // 24.sp
headlineSmall = miuixStyles.title4, // 18.sp headlineSmall = miuixStyles.title3, // 20.sp
titleLarge = miuixStyles.headline1, // 17.sp titleLarge = miuixStyles.title4, // 18.sp
titleMedium = miuixStyles.headline2, // 16.sp titleMedium = miuixStyles.headline2, // 16.sp
titleSmall = miuixStyles.subtitle, // 14.sp, Bold titleSmall = miuixStyles.subtitle, // 14.sp, Bold
bodyLarge = miuixStyles.paragraph, // 17.sp, lineHeight 1.2em (如果不需要行高也可以换成 main) bodyLarge = miuixStyles.paragraph, // 17.sp
bodyMedium = miuixStyles.body1, // 16.sp bodyMedium = miuixStyles.body1, // 16.sp
bodySmall = miuixStyles.body2, // 14.sp bodySmall = miuixStyles.body2.copy(fontSize = 12.sp), // 12.sp
labelLarge = miuixStyles.button, // 17.sp labelLarge = miuixStyles.footnote1.copy(fontSize = 14.sp), // 14.sp
labelMedium = miuixStyles.footnote1, // 13.sp labelMedium = miuixStyles.footnote1, // 13.sp
labelSmall = miuixStyles.footnote2, // 11.sp labelSmall = miuixStyles.footnote2, // 11.sp
bodyLargeEmphasized = miuixStyles.paragraph.copy(fontWeight = FontWeight.Medium), bodyLargeEmphasized = miuixStyles.paragraph.copy(fontWeight = FontWeight.Medium),
bodyMediumEmphasized = miuixStyles.body1.copy(fontWeight = FontWeight.Medium), bodyMediumEmphasized = miuixStyles.body1.copy(fontWeight = FontWeight.Medium),
bodySmallEmphasized = miuixStyles.body2.copy(fontWeight = FontWeight.Medium), bodySmallEmphasized = miuixStyles.body2.copy(
fontSize = 12.sp,
fontWeight = FontWeight.Medium
),
labelLargeEmphasized = miuixStyles.button.copy(fontWeight = FontWeight.Medium), labelLargeEmphasized = miuixStyles.button.copy(
fontSize = 14.sp,
fontWeight = FontWeight.Medium
),
labelMediumEmphasized = miuixStyles.footnote1.copy(fontWeight = FontWeight.Medium), labelMediumEmphasized = miuixStyles.footnote1.copy(fontWeight = FontWeight.Medium),
labelSmallEmphasized = miuixStyles.footnote2.copy(fontWeight = FontWeight.Medium) labelSmallEmphasized = miuixStyles.footnote2.copy(fontWeight = FontWeight.Medium)
) )
} }
@@ -3,6 +3,7 @@ package io.legado.app.ui.widget.components
import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
@@ -22,6 +23,7 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
@@ -272,6 +274,61 @@ fun AppTextFieldSurface(
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppDenseTextField(
state: TextFieldState,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
backgroundColor: Color = Color.Unspecified,
label: String? = null,
labelPosition: TextFieldLabelPosition = TextFieldLabelPosition.Attached(),
placeholder: @Composable (() -> Unit)? = null,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
prefix: @Composable (() -> Unit)? = null,
suffix: @Composable (() -> Unit)? = null,
supportingText: @Composable (() -> Unit)? = null,
isError: Boolean = false,
inputTransformation: InputTransformation? = null,
outputTransformation: OutputTransformation? = null,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
onKeyboardAction: KeyboardActionHandler? = null,
lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
scrollState: ScrollState = rememberScrollState(),
shape: Shape = TextFieldDefaults.shape,
interactionSource: MutableInteractionSource? = null,
) {
AppTextField(
state = state,
modifier = modifier.heightIn(min = 48.dp),
enabled = enabled,
readOnly = readOnly,
backgroundColor = backgroundColor,
label = label,
labelPosition = labelPosition,
placeholder = placeholder,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
prefix = prefix,
suffix = suffix,
supportingText = supportingText,
isError = isError,
inputTransformation = inputTransformation,
outputTransformation = outputTransformation,
keyboardOptions = keyboardOptions,
onKeyboardAction = onKeyboardAction,
lineLimits = lineLimits,
onTextLayout = onTextLayout,
scrollState = scrollState,
shape = shape,
contentPadding = PaddingValues(top = 4.dp, bottom = 4.dp, start = 12.dp, end = 12.dp),
interactionSource = interactionSource
)
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AppTextFieldSurface( fun AppTextFieldSurface(
@@ -321,4 +378,4 @@ fun AppTextFieldSurface(
shape = shape, shape = shape,
interactionSource = interactionSource interactionSource = interactionSource
) )
} }
@@ -79,7 +79,7 @@ fun SearchBarSection(
) )
val searchTextField = @Composable { val searchTextField = @Composable {
AppTextField( AppDenseTextField(
state = textFieldState, state = textFieldState,
modifier = modifier, modifier = modifier,
placeholder = { AppText(placeholder) }, placeholder = { AppText(placeholder) },
@@ -106,4 +106,4 @@ fun SearchBarSection(
searchTextField() searchTextField()
} }
} }
} }
@@ -31,6 +31,7 @@ import io.legado.app.ui.widget.components.text.AppText
data class SelectionActions( data class SelectionActions(
val primaryAction: ActionItem, val primaryAction: ActionItem,
val secondaryActions: List<ActionItem> = emptyList(), val secondaryActions: List<ActionItem> = emptyList(),
val onClearSelection: (() -> Unit)? = null,
val onSelectAll: () -> Unit, val onSelectAll: () -> Unit,
val onSelectInvert: () -> Unit, val onSelectInvert: () -> Unit,
) )
@@ -113,4 +114,4 @@ fun SelectionBottomBar(
} }
} }
) )
} }
@@ -88,7 +88,7 @@ fun <T> ListScaffold(
onSearchToggle = onSearchToggle, onSearchToggle = onSearchToggle,
onSearchQueryChange = onSearchQueryChange, onSearchQueryChange = onSearchQueryChange,
searchPlaceholder = searchPlaceholder, searchPlaceholder = searchPlaceholder,
onClearSelection = { selectionActions?.onSelectInvert?.invoke() }, onClearSelection = { selectionActions?.onClearSelection?.invoke() },
topBarActions = topBarActions, topBarActions = topBarActions,
dropDownMenuContent = dropDownMenuContent, dropDownMenuContent = dropDownMenuContent,
bottomContent = bottomContent bottomContent = bottomContent
@@ -101,6 +101,7 @@ fun <T> RuleListScaffold(
floatingActionButton = floatingActionButton, floatingActionButton = floatingActionButton,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
selectionActions = SelectionActions( selectionActions = SelectionActions(
onClearSelection = onClearSelection,
onSelectAll = onSelectAll, onSelectAll = onSelectAll,
onSelectInvert = onSelectInvert, onSelectInvert = onSelectInvert,
primaryAction = ActionItem( primaryAction = ActionItem(
@@ -38,6 +38,13 @@ fun Uri.takePersistablePermissionSafely(
modeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION modeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
): Boolean { ): Boolean {
if (!isContentScheme()) return false if (!isContentScheme()) return false
kotlin.runCatching {
context.contentResolver.takePersistableUriPermission(this, modeFlags)
}.onSuccess {
return true
}.onFailure {
AppLog.put("持久化读写权限失败(组合模式): $this", it)
}
var granted = false var granted = false
if (modeFlags and Intent.FLAG_GRANT_READ_URI_PERMISSION != 0) { if (modeFlags and Intent.FLAG_GRANT_READ_URI_PERMISSION != 0) {
kotlin.runCatching { kotlin.runCatching {