feat(i18n): standardize English and Simplified Chinese localization across UI
feat(i18n): standardize English and Simplified Chinese UI strings - Move hardcoded user-facing text from Compose screens and dialogs into string resources - Add and refine English and zh-rCN translations for settings, theme, about, backup, TOC, and book info flows - Keep internal logs, exceptions, parser text, and comments unchanged unless user-facing - Preserve existing behavior while improving localization consistency
This commit is contained in:
@@ -85,6 +85,9 @@ private fun MaterialAboutScreen(
|
||||
versionName: String,
|
||||
) {
|
||||
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
|
||||
val privacyPolicyTitle = stringResource(R.string.about_privacy_policy_title)
|
||||
val licenseTitle = stringResource(R.string.about_license_title)
|
||||
val disclaimerTitle = stringResource(R.string.about_disclaimer_title)
|
||||
|
||||
AppScaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
@@ -178,7 +181,7 @@ private fun MaterialAboutScreen(
|
||||
onClick = {
|
||||
onIntent(
|
||||
AboutIntent.ShowMdFile(
|
||||
"隐私政策",
|
||||
privacyPolicyTitle,
|
||||
"privacyPolicy.md"
|
||||
)
|
||||
)
|
||||
@@ -188,13 +191,13 @@ private fun MaterialAboutScreen(
|
||||
SettingItemWithDivider {
|
||||
SettingItem(
|
||||
title = stringResource(R.string.license),
|
||||
onClick = { onIntent(AboutIntent.ShowMdFile("许可证", "LICENSE.md")) }
|
||||
onClick = { onIntent(AboutIntent.ShowMdFile(licenseTitle, "LICENSE.md")) }
|
||||
)
|
||||
}
|
||||
SettingItemWithDivider {
|
||||
SettingItem(
|
||||
title = stringResource(R.string.disclaimer),
|
||||
onClick = { onIntent(AboutIntent.ShowMdFile("免责声明", "disclaimer.md")) }
|
||||
onClick = { onIntent(AboutIntent.ShowMdFile(disclaimerTitle, "disclaimer.md")) }
|
||||
)
|
||||
}
|
||||
SettingItemWithDivider {
|
||||
|
||||
@@ -80,7 +80,7 @@ fun UpdateSheet(
|
||||
) {
|
||||
val title = when (mode) {
|
||||
UpdateMode.UPDATE -> stringResource(R.string.check_update)
|
||||
UpdateMode.VIEW_LOG -> "已经更新至"
|
||||
UpdateMode.VIEW_LOG -> stringResource(R.string.about_installed_version_title)
|
||||
}
|
||||
|
||||
AppModalBottomSheet(
|
||||
@@ -99,7 +99,7 @@ fun UpdateSheet(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AppText(
|
||||
text = "当前版本",
|
||||
text = stringResource(R.string.about_current_version),
|
||||
style = LegadoTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
@@ -114,7 +114,7 @@ fun UpdateSheet(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AppText(
|
||||
text = "新版本",
|
||||
text = stringResource(R.string.about_new_version),
|
||||
style = LegadoTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
@@ -144,7 +144,7 @@ fun UpdateSheet(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AppText(
|
||||
text = "渠道",
|
||||
text = stringResource(R.string.about_update_channel),
|
||||
style = LegadoTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
@@ -182,7 +182,7 @@ fun UpdateSheet(
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
PrimaryButton(
|
||||
onClick = onStartDownload,
|
||||
text = "更新",
|
||||
text = stringResource(R.string.about_update_action),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -152,17 +152,17 @@ class AboutViewModel(application: Application) : BaseViewModel(application) {
|
||||
private fun saveLog() {
|
||||
execute {
|
||||
val backupPath = AppConfig.backupPath ?: run {
|
||||
_effects.tryEmit(AboutEffect.ShowToast("未设置备份目录"))
|
||||
_effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_backup_dir_not_set)))
|
||||
return@execute
|
||||
}
|
||||
if (!AppConfig.recordLog) {
|
||||
_effects.tryEmit(AboutEffect.ShowToast("未开启日志记录,请去其他设置里打开记录日志"))
|
||||
_effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_log_recording_disabled)))
|
||||
delay(3000)
|
||||
}
|
||||
val doc = FileDoc.fromUri(backupPath.toUri(), true)
|
||||
copyLogs(doc)
|
||||
copyHeapDump(doc)
|
||||
_effects.tryEmit(AboutEffect.ShowToast("已保存至备份目录"))
|
||||
_effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_saved_to_backup_dir)))
|
||||
}.onError {
|
||||
AppLog.put("保存日志出错\n${it.localizedMessage}", it, true)
|
||||
}
|
||||
@@ -171,21 +171,21 @@ class AboutViewModel(application: Application) : BaseViewModel(application) {
|
||||
private fun createHeapDump() {
|
||||
execute {
|
||||
val backupPath = AppConfig.backupPath ?: run {
|
||||
_effects.tryEmit(AboutEffect.ShowToast("未设置备份目录"))
|
||||
_effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_backup_dir_not_set)))
|
||||
return@execute
|
||||
}
|
||||
if (!AppConfig.recordHeapDump) {
|
||||
_effects.tryEmit(AboutEffect.ShowToast("未开启堆转储记录,请去其他设置里打开记录堆转储"))
|
||||
_effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_heap_dump_recording_disabled)))
|
||||
delay(3000)
|
||||
}
|
||||
_effects.tryEmit(AboutEffect.ShowToast("开始创建堆转储"))
|
||||
_effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_heap_dump_creating)))
|
||||
System.gc()
|
||||
CrashHandler.doHeapDump(true)
|
||||
val doc = FileDoc.fromUri(backupPath.toUri(), true)
|
||||
if (!copyHeapDump(doc)) {
|
||||
_effects.tryEmit(AboutEffect.ShowToast("未找到堆转储文件"))
|
||||
_effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_heap_dump_not_found)))
|
||||
} else {
|
||||
_effects.tryEmit(AboutEffect.ShowToast("已保存至备份目录"))
|
||||
_effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_saved_to_backup_dir)))
|
||||
}
|
||||
}.onError {
|
||||
AppLog.put("保存堆转储失败\n${it.localizedMessage}", it)
|
||||
@@ -197,7 +197,7 @@ class AboutViewModel(application: Application) : BaseViewModel(application) {
|
||||
if (sheet is AboutSheet.Update) {
|
||||
val info = sheet.updateInfo
|
||||
if (info.downloadUrl.isBlank() || info.fileName.isBlank()) {
|
||||
_effects.tryEmit(AboutEffect.ShowToast("下载信息不完整"))
|
||||
_effects.tryEmit(AboutEffect.ShowToast(context.getString(R.string.about_download_info_incomplete)))
|
||||
} else {
|
||||
_effects.tryEmit(AboutEffect.StartDownload(info.downloadUrl, info.fileName))
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ fun MiuixAboutScreen(
|
||||
val topAppBarScrollBehavior = MiuixScrollBehavior()
|
||||
val lazyListState = rememberLazyListState()
|
||||
val isWideScreen = shouldShowSplitPane()
|
||||
val licenseTitle = stringResource(R.string.about_license_title)
|
||||
|
||||
val scrollProgress by remember {
|
||||
derivedStateOf {
|
||||
@@ -119,7 +120,7 @@ fun MiuixAboutScreen(
|
||||
if (scrollProgress == 1f) MiuixTheme.colorScheme.surface else Color.Transparent
|
||||
}
|
||||
|
||||
val navigator = remember {
|
||||
val navigator = remember(licenseTitle) {
|
||||
object : MiuixNavigator {
|
||||
override fun pop() {
|
||||
onBack()
|
||||
@@ -127,7 +128,7 @@ fun MiuixAboutScreen(
|
||||
|
||||
override fun push(route: Any) {
|
||||
if (route == "License") {
|
||||
onIntent(AboutIntent.ShowMdFile("许可证", "LICENSE.md"))
|
||||
onIntent(AboutIntent.ShowMdFile(licenseTitle, "LICENSE.md"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,6 +181,9 @@ private fun AboutContent(
|
||||
) {
|
||||
val appState = LocalAppState.current
|
||||
val isWideScreen = LocalIsWideScreen.current
|
||||
val privacyPolicyTitle = stringResource(R.string.about_privacy_policy_title)
|
||||
val licenseTitle = stringResource(R.string.about_license_title)
|
||||
val disclaimerTitle = stringResource(R.string.about_disclaimer_title)
|
||||
|
||||
val backdrop = rememberBlurBackdrop()
|
||||
var blurRadius by remember { mutableFloatStateOf(60f) }
|
||||
@@ -427,7 +431,7 @@ private fun AboutContent(
|
||||
onClick = {
|
||||
onIntent(
|
||||
AboutIntent.ShowMdFile(
|
||||
"隐私政策",
|
||||
privacyPolicyTitle,
|
||||
"privacyPolicy.md"
|
||||
)
|
||||
)
|
||||
@@ -435,14 +439,14 @@ private fun AboutContent(
|
||||
)
|
||||
ArrowPreference(
|
||||
title = stringResource(R.string.license),
|
||||
onClick = { onIntent(AboutIntent.ShowMdFile("许可证", "LICENSE.md")) },
|
||||
onClick = { onIntent(AboutIntent.ShowMdFile(licenseTitle, "LICENSE.md")) },
|
||||
)
|
||||
ArrowPreference(
|
||||
title = stringResource(R.string.disclaimer),
|
||||
onClick = {
|
||||
onIntent(
|
||||
AboutIntent.ShowMdFile(
|
||||
"免责声明",
|
||||
disclaimerTitle,
|
||||
"disclaimer.md"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -55,7 +55,7 @@ class UpdateDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_update) {
|
||||
|
||||
val updateBody = arguments?.getString("updateBody")
|
||||
if (updateBody.isNullOrBlank()) {
|
||||
toastOnUi("没有数据")
|
||||
toastOnUi(R.string.about_no_data)
|
||||
dismiss()
|
||||
return
|
||||
}
|
||||
@@ -77,16 +77,16 @@ class UpdateDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_update) {
|
||||
binding.tvUrl.text = url
|
||||
binding.btnUpdate.setOnClickListener {
|
||||
if (url.isNullOrBlank() || fileName.isNullOrBlank()) {
|
||||
toastOnUi("下载信息不完整")
|
||||
toastOnUi(R.string.about_download_info_incomplete)
|
||||
return@setOnClickListener
|
||||
}
|
||||
Download.start(requireContext(), url, fileName)
|
||||
toastOnUi("开始下载: $fileName")
|
||||
toastOnUi(getString(R.string.about_start_download, fileName))
|
||||
}
|
||||
}
|
||||
|
||||
Mode.VIEW_LOG -> {
|
||||
binding.bottomSheetTitle.text = "已经更新至"
|
||||
binding.bottomSheetTitle.setText(R.string.about_installed_version_title)
|
||||
binding.tvVersion.text = BuildConfig.VERSION_NAME
|
||||
binding.btnUpdate.gone()
|
||||
binding.llCurrent.gone()
|
||||
|
||||
@@ -178,7 +178,11 @@ fun BookInfoEditContent(
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
BookTypeDropdown(
|
||||
bookTypes = uiState.bookTypes,
|
||||
bookTypes = listOf(
|
||||
BookInfoEditType.TEXT to stringResource(R.string.book_type_text),
|
||||
BookInfoEditType.AUDIO to stringResource(R.string.book_type_audio),
|
||||
BookInfoEditType.IMAGE to stringResource(R.string.book_type_image)
|
||||
),
|
||||
selectedType = uiState.selectedType,
|
||||
onTypeSelected = { viewModel.onBookTypeChange(it) }
|
||||
)
|
||||
@@ -186,8 +190,8 @@ fun BookInfoEditContent(
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
SwitchSettingItem(
|
||||
title = "固定书籍类型",
|
||||
description = "书籍更新后不覆盖书籍类型",
|
||||
title = stringResource(R.string.fixed_book_type),
|
||||
description = stringResource(R.string.fixed_book_type_summary),
|
||||
checked = uiState.fixedType,
|
||||
onCheckedChange = { viewModel.onFixedTypeChange(it) }
|
||||
)
|
||||
@@ -201,7 +205,7 @@ fun BookInfoEditContent(
|
||||
AppTextField(
|
||||
value = uiState.name,
|
||||
onValueChange = { viewModel.onNameChange(it) },
|
||||
label = "书名",
|
||||
label = stringResource(R.string.book_name),
|
||||
backgroundColor = inputBackgroundColor,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
@@ -209,7 +213,7 @@ fun BookInfoEditContent(
|
||||
AppTextField(
|
||||
value = uiState.author,
|
||||
onValueChange = { viewModel.onAuthorChange(it) },
|
||||
label = "作者",
|
||||
label = stringResource(R.string.author),
|
||||
backgroundColor = inputBackgroundColor,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
@@ -217,7 +221,7 @@ fun BookInfoEditContent(
|
||||
AppTextField(
|
||||
value = uiState.coverUrl ?: "",
|
||||
onValueChange = { viewModel.onCoverUrlChange(it) },
|
||||
label = "封面链接",
|
||||
label = stringResource(R.string.cover_url),
|
||||
backgroundColor = inputBackgroundColor,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
@@ -232,7 +236,7 @@ fun BookInfoEditContent(
|
||||
AppTextField(
|
||||
value = uiState.intro ?: "",
|
||||
onValueChange = { viewModel.onIntroChange(it) },
|
||||
label = "简介",
|
||||
label = stringResource(R.string.book_intro),
|
||||
backgroundColor = inputBackgroundColor,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
@@ -240,7 +244,7 @@ fun BookInfoEditContent(
|
||||
AppTextField(
|
||||
value = uiState.remark ?: "",
|
||||
onValueChange = { viewModel.onRemarkChange(it) },
|
||||
label = "备注",
|
||||
label = stringResource(R.string.book_remark),
|
||||
backgroundColor = inputBackgroundColor,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
@@ -250,18 +254,19 @@ fun BookInfoEditContent(
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BookTypeDropdown(
|
||||
bookTypes: List<String>,
|
||||
selectedType: String,
|
||||
onTypeSelected: (String) -> Unit
|
||||
bookTypes: List<Pair<BookInfoEditType, String>>,
|
||||
selectedType: BookInfoEditType,
|
||||
onTypeSelected: (BookInfoEditType) -> Unit
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val selectedTypeLabel = bookTypes.firstOrNull { it.first == selectedType }?.second.orEmpty()
|
||||
|
||||
val textFieldState = rememberTextFieldState(
|
||||
initialText = selectedType
|
||||
initialText = selectedTypeLabel
|
||||
)
|
||||
|
||||
LaunchedEffect(selectedType) {
|
||||
textFieldState.setTextAndPlaceCursorAtEnd(selectedType)
|
||||
LaunchedEffect(selectedTypeLabel) {
|
||||
textFieldState.setTextAndPlaceCursorAtEnd(selectedTypeLabel)
|
||||
}
|
||||
|
||||
val bookInfoInputColor = ThemeConfig.bookInfoInputColor
|
||||
@@ -280,7 +285,7 @@ fun BookTypeDropdown(
|
||||
state = textFieldState,
|
||||
readOnly = true,
|
||||
lineLimits = TextFieldLineLimits.SingleLine,
|
||||
label = "书籍类型",
|
||||
label = stringResource(R.string.book_type_label),
|
||||
backgroundColor = inputBackgroundColor,
|
||||
trailingIcon = {
|
||||
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
|
||||
@@ -297,11 +302,11 @@ fun BookTypeDropdown(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
bookTypes.forEach { option ->
|
||||
bookTypes.forEach { (type, label) ->
|
||||
RoundDropdownMenuItem(
|
||||
text = option,
|
||||
text = label,
|
||||
onClick = {
|
||||
onTypeSelected(option)
|
||||
onTypeSelected(type)
|
||||
expanded = false
|
||||
}
|
||||
)
|
||||
@@ -387,12 +392,16 @@ fun KindEditor(
|
||||
AppAlertDialog(
|
||||
show = true,
|
||||
onDismissRequest = { editingIndex = null },
|
||||
title = if (isAdding) "添加标签" else "编辑标签",
|
||||
title = if (isAdding) {
|
||||
stringResource(R.string.add_tag)
|
||||
} else {
|
||||
stringResource(R.string.edit_tag)
|
||||
},
|
||||
content = {
|
||||
AppTextField(
|
||||
value = editText,
|
||||
onValueChange = { editText = it },
|
||||
label = "标签",
|
||||
label = stringResource(R.string.tag),
|
||||
backgroundColor = backgroundColor,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
@@ -25,6 +25,12 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
enum class BookInfoEditType {
|
||||
TEXT,
|
||||
AUDIO,
|
||||
IMAGE
|
||||
}
|
||||
|
||||
data class BookInfoEditUiState(
|
||||
val name: String = "",
|
||||
val author: String = "",
|
||||
@@ -33,8 +39,7 @@ data class BookInfoEditUiState(
|
||||
val remark: String? = null,
|
||||
val kindList: List<String> = emptyList(),
|
||||
val originalKindList: List<String> = emptyList(),
|
||||
val selectedType: String = "文本",
|
||||
val bookTypes: List<String> = listOf("文本", "音频", "图片"),
|
||||
val selectedType: BookInfoEditType = BookInfoEditType.TEXT,
|
||||
val fixedType: Boolean = false,
|
||||
val book: Book? = null,
|
||||
)
|
||||
@@ -49,9 +54,9 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
|
||||
book = appDb.bookDao.getBook(bookUrl)
|
||||
book?.let {
|
||||
val selectedTypeIndex = when {
|
||||
it.isImage -> 2
|
||||
it.isAudio -> 1
|
||||
else -> 0
|
||||
it.isImage -> BookInfoEditType.IMAGE
|
||||
it.isAudio -> BookInfoEditType.AUDIO
|
||||
else -> BookInfoEditType.TEXT
|
||||
}
|
||||
val kinds =
|
||||
it.kind?.split(",", "\n")?.filter { kind -> kind.isNotBlank() }.orEmpty()
|
||||
@@ -63,7 +68,7 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
|
||||
remark = it.remark,
|
||||
kindList = kinds,
|
||||
originalKindList = kinds,
|
||||
selectedType = _uiState.value.bookTypes[selectedTypeIndex],
|
||||
selectedType = selectedTypeIndex,
|
||||
fixedType = it.config.fixedType,
|
||||
book = it
|
||||
)
|
||||
@@ -99,7 +104,7 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
|
||||
_uiState.value = _uiState.value.copy(kindList = kindList)
|
||||
}
|
||||
|
||||
fun onBookTypeChange(bookType: String) {
|
||||
fun onBookTypeChange(bookType: BookInfoEditType) {
|
||||
_uiState.value = _uiState.value.copy(selectedType = bookType)
|
||||
}
|
||||
|
||||
@@ -121,8 +126,8 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
|
||||
book.remark = currentState.remark
|
||||
val local = if (book.isLocal) BookType.local else 0
|
||||
val bookType = when (currentState.selectedType) {
|
||||
currentState.bookTypes[2] -> BookType.image or local
|
||||
currentState.bookTypes[1] -> BookType.audio or local
|
||||
BookInfoEditType.IMAGE -> BookType.image or local
|
||||
BookInfoEditType.AUDIO -> BookType.audio or local
|
||||
else -> BookType.text or local
|
||||
}
|
||||
book.removeType(BookType.local, BookType.image, BookType.audio, BookType.text)
|
||||
|
||||
@@ -40,4 +40,4 @@ class TocActivity : BaseComposeActivity() {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,12 +71,14 @@ import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import io.legado.app.R
|
||||
import io.legado.app.data.entities.Bookmark
|
||||
import io.legado.app.help.book.isLocal
|
||||
import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
|
||||
@@ -151,6 +153,15 @@ fun TocScreen(
|
||||
|
||||
val useReplace = viewModel.useReplace
|
||||
val showWordCount = viewModel.showWordCount
|
||||
val bookmarkManagementTitle = stringResource(R.string.bookmark_management)
|
||||
val locateCurrentReadingText = stringResource(R.string.locate_current_reading)
|
||||
val moveToTopText = stringResource(R.string.move_to_top)
|
||||
val moveToBottomText = stringResource(R.string.move_to_bottom)
|
||||
val downloadAllText = stringResource(R.string.download_all)
|
||||
val invertSelectionText = stringResource(R.string.invert_selection)
|
||||
val selectFollowingText = stringResource(R.string.select_following)
|
||||
val addBookmarkText = stringResource(R.string.bookmark_add)
|
||||
val bookmarkDefaultFileName = stringResource(R.string.bookmark)
|
||||
|
||||
val topBarTitle = remember(
|
||||
pagerState.currentPage,
|
||||
@@ -162,7 +173,7 @@ fun TocScreen(
|
||||
book?.durChapterTitle?.takeIf { it.isNotBlank() } ?: (book?.name ?: "")
|
||||
}
|
||||
|
||||
1 -> "书签管理"
|
||||
1 -> bookmarkManagementTitle
|
||||
else -> book?.name ?: ""
|
||||
}
|
||||
}
|
||||
@@ -206,9 +217,15 @@ fun TocScreen(
|
||||
}
|
||||
}
|
||||
|
||||
val fabItems = remember(state.items) {
|
||||
val fabItems = remember(
|
||||
state.items,
|
||||
locateCurrentReadingText,
|
||||
moveToTopText,
|
||||
moveToBottomText,
|
||||
downloadAllText
|
||||
) {
|
||||
listOf(
|
||||
FabMenuItem(Icons.Default.LocationOn, "定位至当前阅读") {
|
||||
FabMenuItem(Icons.Default.LocationOn, locateCurrentReadingText) {
|
||||
scope.launch {
|
||||
val target = state.items.indexOfFirst { it.isDur }
|
||||
if (target != -1) {
|
||||
@@ -219,32 +236,37 @@ fun TocScreen(
|
||||
}
|
||||
}
|
||||
},
|
||||
FabMenuItem(Icons.Default.VerticalAlignTop, "移至顶部") {
|
||||
FabMenuItem(Icons.Default.VerticalAlignTop, moveToTopText) {
|
||||
scope.launch { listState.animateScrollToItem(0) }
|
||||
},
|
||||
FabMenuItem(Icons.Default.VerticalAlignBottom, "移至底部") {
|
||||
FabMenuItem(Icons.Default.VerticalAlignBottom, moveToBottomText) {
|
||||
scope.launch { listState.animateScrollToItem(state.items.size) }
|
||||
},
|
||||
FabMenuItem(Icons.Default.DownloadForOffline, "下载全部") {
|
||||
FabMenuItem(Icons.Default.DownloadForOffline, downloadAllText) {
|
||||
viewModel.downloadAll()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val selectionSecondaryActions = remember(state.selectedIds) {
|
||||
val selectionSecondaryActions = remember(
|
||||
state.selectedIds,
|
||||
invertSelectionText,
|
||||
selectFollowingText,
|
||||
addBookmarkText
|
||||
) {
|
||||
listOf(
|
||||
ActionItem(
|
||||
text = "反选",
|
||||
text = invertSelectionText,
|
||||
icon = Icons.Default.Refresh,
|
||||
onClick = { viewModel.invertSelection() }
|
||||
),
|
||||
ActionItem(
|
||||
text = "选择后续",
|
||||
text = selectFollowingText,
|
||||
icon = Icons.Default.ExpandMore,
|
||||
onClick = { viewModel.selectFromLast() }
|
||||
),
|
||||
ActionItem(
|
||||
text = "添加书签",
|
||||
text = addBookmarkText,
|
||||
icon = Icons.Default.BookmarkAdd,
|
||||
onClick = { viewModel.addBookmarksForSelected() }
|
||||
)
|
||||
@@ -332,35 +354,35 @@ fun TocScreen(
|
||||
onBackClick = onBackClick,
|
||||
onSearchToggle = { viewModel.setSearchMode(it) },
|
||||
onSearchQueryChange = { viewModel.setSearchKey(it) },
|
||||
searchPlaceholder = "搜索章节...",
|
||||
searchPlaceholder = stringResource(R.string.search_chapters),
|
||||
onClearSelection = { viewModel.clearSelection() },
|
||||
dropDownMenuContent = { dismiss ->
|
||||
when (pagerState.currentPage) {
|
||||
0 -> {
|
||||
RoundDropdownMenuItem(
|
||||
text = "使用替换规则",
|
||||
text = stringResource(R.string.use_replace_rule),
|
||||
isSelected = useReplace,
|
||||
onClick = { viewModel.toggleUseReplace() }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = "显示字数",
|
||||
text = stringResource(R.string.show_word_count),
|
||||
isSelected = showWordCount,
|
||||
onClick = { viewModel.toggleShowWordCount() }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = "反转目录",
|
||||
text = stringResource(R.string.reverse_toc),
|
||||
onClick = { viewModel.reverseToc() }
|
||||
)
|
||||
PillDivider()
|
||||
RoundDropdownMenuItem(
|
||||
text = "替换规则",
|
||||
text = stringResource(R.string.replace_rule_title),
|
||||
onClick = {
|
||||
onOpenReplaceRule(null)
|
||||
dismiss()
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = "新建替换规则",
|
||||
text = stringResource(R.string.add_replace_rule),
|
||||
onClick = {
|
||||
val scopes = mutableListOf<String>()
|
||||
book?.name?.let { scopes.add(it) }
|
||||
@@ -378,9 +400,9 @@ fun TocScreen(
|
||||
}
|
||||
)
|
||||
if (book?.isLocal == true) {
|
||||
PillHeaderDivider(title = "本地书籍选项")
|
||||
PillHeaderDivider(title = stringResource(R.string.local_book_options))
|
||||
RoundDropdownMenuItem(
|
||||
text = "本地书籍目录规则",
|
||||
text = stringResource(R.string.local_book_toc_rule),
|
||||
onClick = {
|
||||
val intent =
|
||||
Intent(context, TxtTocRuleActivity::class.java).apply {
|
||||
@@ -391,7 +413,7 @@ fun TocScreen(
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = "拆分超长章节",
|
||||
text = stringResource(R.string.split_long_chapters),
|
||||
isSelected = viewModel.isSplitLongChapter,
|
||||
onClick = {
|
||||
viewModel.toggleSplitLongChapter()
|
||||
@@ -403,25 +425,27 @@ fun TocScreen(
|
||||
|
||||
else -> {
|
||||
RoundDropdownMenuItem(
|
||||
text = "导出书签为JSON",
|
||||
text = stringResource(R.string.export_bookmarks_json),
|
||||
onClick = {
|
||||
val dateFormat = SimpleDateFormat(
|
||||
"yyyyMMdd_HHmm",
|
||||
Locale.getDefault()
|
||||
).format(Date())
|
||||
val initialName = "${book?.name ?: "书签"}_$dateFormat.json"
|
||||
val initialName =
|
||||
"${book?.name ?: bookmarkDefaultFileName}_$dateFormat.json"
|
||||
exportLauncher.launch(initialName)
|
||||
dismiss()
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = "导出书签为MarkDown",
|
||||
text = stringResource(R.string.export_bookmarks_markdown),
|
||||
onClick = {
|
||||
val dateFormat = SimpleDateFormat(
|
||||
"yyyyMMdd_HHmm",
|
||||
Locale.getDefault()
|
||||
).format(Date())
|
||||
val initialName = "${book?.name ?: "书签"}_$dateFormat.md"
|
||||
val initialName =
|
||||
"${book?.name ?: bookmarkDefaultFileName}_$dateFormat.md"
|
||||
exportLauncher.launch(initialName)
|
||||
dismiss()
|
||||
}
|
||||
@@ -437,7 +461,10 @@ fun TocScreen(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AppTabRow(
|
||||
tabTitles = listOf("目录", "书签"),
|
||||
tabTitles = listOf(
|
||||
stringResource(R.string.chapter_list),
|
||||
stringResource(R.string.bookmark)
|
||||
),
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
onTabSelected = { index ->
|
||||
scope.launch {
|
||||
@@ -453,20 +480,20 @@ fun TocScreen(
|
||||
checked = showVolumeMenu,
|
||||
onCheckedChange = { showVolumeMenu = it },
|
||||
imageVector = Icons.AutoMirrored.Filled.FormatListBulleted,
|
||||
contentDescription = "卷管理"
|
||||
contentDescription = stringResource(R.string.volume_management)
|
||||
)
|
||||
RoundDropdownMenu(
|
||||
expanded = showVolumeMenu,
|
||||
onDismissRequest = { showVolumeMenu = false }
|
||||
) {
|
||||
RoundDropdownMenuItem(
|
||||
text = "展开所有卷",
|
||||
text = stringResource(R.string.expand_volume),
|
||||
onClick = {
|
||||
viewModel.expandAllVolumes(); showVolumeMenu = false
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = "收起所有卷",
|
||||
text = stringResource(R.string.coll_volume),
|
||||
onClick = {
|
||||
viewModel.collapseAllVolumes(); showVolumeMenu = false
|
||||
}
|
||||
@@ -475,7 +502,7 @@ fun TocScreen(
|
||||
val volumeItems =
|
||||
remember(state.items) { state.items.filter { it.isVolume } }
|
||||
if (volumeItems.isNotEmpty()) {
|
||||
PillHeaderDivider(title = "快速跳转")
|
||||
PillHeaderDivider(title = stringResource(R.string.quick_jump))
|
||||
volumeItems.forEach { uiItem ->
|
||||
RoundDropdownMenuItem(
|
||||
text = uiItem.title,
|
||||
@@ -528,7 +555,10 @@ fun TocScreen(
|
||||
onSelectAll = { viewModel.selectAll() },
|
||||
onSelectInvert = { viewModel.invertSelection() },
|
||||
primaryAction = ActionItem(
|
||||
text = "下载已选 (${state.selectedIds.size})",
|
||||
text = stringResource(
|
||||
R.string.download_selected_count,
|
||||
state.selectedIds.size
|
||||
),
|
||||
icon = Icons.Default.Download,
|
||||
onClick = { viewModel.downloadSelected() }
|
||||
),
|
||||
@@ -818,7 +848,7 @@ fun BookmarkListContent(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
EmptyMessage(
|
||||
message = "暂无书签"
|
||||
message = stringResource(R.string.no_bookmark)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.compose.runtime.snapshotFlow
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseRuleViewModel
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.Book
|
||||
@@ -424,9 +425,13 @@ class TocViewModel(
|
||||
val book = bookState.value ?: return
|
||||
book.tocUrl = newRegex
|
||||
upBookTocRule(book) { error ->
|
||||
if (error != null) context.toastOnUi("更新目录规则失败: ${error.localizedMessage}")
|
||||
if (error != null) {
|
||||
context.toastOnUi(
|
||||
context.getString(R.string.toc_rule_update_failed, error.localizedMessage)
|
||||
)
|
||||
}
|
||||
else {
|
||||
context.toastOnUi("目录规则已更新")
|
||||
context.toastOnUi(R.string.toc_rule_updated)
|
||||
if (ReadBook.book?.bookUrl == book.bookUrl) ReadBook.upMsg(null)
|
||||
}
|
||||
}
|
||||
@@ -437,8 +442,14 @@ class TocViewModel(
|
||||
val newState = !isSplitLongChapter
|
||||
book.setSplitLongChapter(newState)
|
||||
upBookTocRule(book) { error ->
|
||||
if (error != null) context.toastOnUi("设置失败: ${error.localizedMessage}")
|
||||
else context.toastOnUi(if (newState) "已开启长章节拆分" else "已关闭长章节拆分")
|
||||
if (error != null) {
|
||||
context.toastOnUi(context.getString(R.string.setting_failed, error.localizedMessage))
|
||||
} else {
|
||||
context.toastOnUi(
|
||||
if (newState) R.string.split_long_chapters_enabled
|
||||
else R.string.split_long_chapters_disabled
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,16 +478,16 @@ class TocViewModel(
|
||||
val book = bookState.value ?: return@launch
|
||||
val bookmarks = appDb.bookmarkDao.getByBook(book.name, book.author)
|
||||
if (bookmarks.isEmpty()) {
|
||||
context.toastOnUi("没有可导出的书签")
|
||||
context.toastOnUi(R.string.no_bookmarks_to_export)
|
||||
return@launch
|
||||
}
|
||||
BookmarkExporter.exportToUri(
|
||||
context = getApplication(), fileUri = fileUri, bookmarks = bookmarks,
|
||||
isMd = isMd, bookName = book.name, author = book.author
|
||||
)
|
||||
context.toastOnUi("保存成功")
|
||||
context.toastOnUi(R.string.save_success)
|
||||
} catch (e: Exception) {
|
||||
context.toastOnUi("保存失败: ${e.message}")
|
||||
context.toastOnUi(context.getString(R.string.save_failed_with_error, e.message))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,7 +506,7 @@ class TocViewModel(
|
||||
.toList()
|
||||
|
||||
if (selectedItems.isEmpty()) {
|
||||
context.toastOnUi("请选择章节")
|
||||
context.toastOnUi(R.string.select_chapters)
|
||||
return@launch
|
||||
}
|
||||
|
||||
@@ -512,7 +523,7 @@ class TocViewModel(
|
||||
}
|
||||
|
||||
appDb.bookmarkDao.insert(*bookmarks.toTypedArray())
|
||||
context.toastOnUi("已添加 ${bookmarks.size} 个书签")
|
||||
context.toastOnUi(context.getString(R.string.bookmarks_added_count, bookmarks.size))
|
||||
withContext(Dispatchers.Main) {
|
||||
clearSelection()
|
||||
}
|
||||
@@ -525,7 +536,9 @@ class TocViewModel(
|
||||
execute {
|
||||
cacheBookChaptersUseCase.execute(book.bookUrl, indices)
|
||||
}.onSuccess { count ->
|
||||
getApplication<Application>().toastOnUi("开始下载 $count 个章节")
|
||||
getApplication<Application>().toastOnUi(
|
||||
context.getString(R.string.start_downloading_chapters, count)
|
||||
)
|
||||
clearSelection()
|
||||
}
|
||||
}
|
||||
@@ -535,7 +548,7 @@ class TocViewModel(
|
||||
execute {
|
||||
cacheBookChaptersUseCase.execute(book.bookUrl, listOf(index))
|
||||
}.onSuccess {
|
||||
getApplication<Application>().toastOnUi("开始下载章节")
|
||||
getApplication<Application>().toastOnUi(R.string.start_downloading_chapter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,14 +559,16 @@ class TocViewModel(
|
||||
.map { it.id }
|
||||
|
||||
if (targetIndices.isEmpty()) {
|
||||
getApplication<Application>().toastOnUi("所有章节已缓存")
|
||||
getApplication<Application>().toastOnUi(R.string.all_chapters_cached)
|
||||
return
|
||||
}
|
||||
|
||||
execute {
|
||||
cacheBookChaptersUseCase.execute(book.bookUrl, targetIndices)
|
||||
}.onSuccess { count ->
|
||||
getApplication<Application>().toastOnUi("开始下载剩余 $count 个章节")
|
||||
getApplication<Application>().toastOnUi(
|
||||
context.getString(R.string.start_downloading_remaining_chapters, count)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ fun TxtRuleScreen(
|
||||
)
|
||||
|
||||
BatchImportDialog(
|
||||
title = "导入词典规则",
|
||||
title = stringResource(R.string.import_txt_toc_rule),
|
||||
importState = importState,
|
||||
onDismissRequest = { viewModel.cancelImport() },
|
||||
onToggleItem = { viewModel.toggleImportSelection(it) },
|
||||
@@ -262,7 +262,11 @@ fun TxtRuleScreen(
|
||||
)
|
||||
|
||||
RuleListScaffold(
|
||||
title = if (isPickMode) "选择目录规则" else "目录规则",
|
||||
title = if (isPickMode) {
|
||||
stringResource(R.string.select_toc_rule)
|
||||
} else {
|
||||
stringResource(R.string.txt_toc_rule)
|
||||
},
|
||||
state = uiState,
|
||||
onBackClick = { onBackClick() },
|
||||
onSearchToggle = { active ->
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.legado.app.ui.book.toc.rule
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseRuleViewModel
|
||||
import io.legado.app.data.entities.TxtTocRule
|
||||
import io.legado.app.data.repository.TxtTocRuleRepository
|
||||
@@ -147,14 +148,14 @@ class TxtTocRuleViewModel(
|
||||
fun pasteRule(): TxtTocRule? {
|
||||
val text = context.getClipText()
|
||||
if (text.isNullOrBlank()) {
|
||||
context.toastOnUi("剪贴板没有内容")
|
||||
context.toastOnUi(R.string.clipboard_empty)
|
||||
return null
|
||||
}
|
||||
return try {
|
||||
GSON.fromJsonObject<TxtTocRule>(text).getOrThrow()
|
||||
} catch (e: Exception) {
|
||||
context.toastOnUi("格式不对")
|
||||
context.toastOnUi(R.string.invalid_format)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,15 +168,17 @@ fun BackupConfigScreen(
|
||||
) { uri ->
|
||||
uri?.let {
|
||||
showLoadingDialog = true
|
||||
loadingText = "恢复中…"
|
||||
loadingText = context.getString(R.string.on_restore)
|
||||
scope.launch {
|
||||
try {
|
||||
Restore.restore(context, uri)
|
||||
showLoadingDialog = false
|
||||
snackbarHostState.showSnackbar("恢复成功")
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.restore_success))
|
||||
} catch (e: Exception) {
|
||||
showLoadingDialog = false
|
||||
snackbarHostState.showSnackbar("恢复出错: ${e.localizedMessage}")
|
||||
snackbarHostState.showSnackbar(
|
||||
context.getString(R.string.restore_fail_with_error, e.localizedMessage)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,20 +362,24 @@ fun BackupConfigScreen(
|
||||
value = tempAccount,
|
||||
onValueChange = { tempAccount = it },
|
||||
backgroundColor = LegadoTheme.colorScheme.surface,
|
||||
label = "账号"
|
||||
label = stringResource(R.string.web_dav_account)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
AppTextField(
|
||||
value = tempPassword,
|
||||
onValueChange = { tempPassword = it },
|
||||
backgroundColor = MiuixTheme.colorScheme.surface,
|
||||
label = "密码",
|
||||
label = stringResource(R.string.web_dav_pw),
|
||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
trailingIcon = {
|
||||
val image =
|
||||
if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
|
||||
val description = if (passwordVisible) "隐藏密码" else "显示密码"
|
||||
val description = if (passwordVisible) {
|
||||
stringResource(R.string.hide_password)
|
||||
} else {
|
||||
stringResource(R.string.show_password)
|
||||
}
|
||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||
Icon(imageVector = image, contentDescription = description)
|
||||
}
|
||||
@@ -387,13 +393,13 @@ fun BackupConfigScreen(
|
||||
showWebDavAuthDialog = false
|
||||
scope.launch {
|
||||
showLoadingDialog = true
|
||||
loadingText = "测试中…"
|
||||
loadingText = context.getString(R.string.test_sync_loading_text)
|
||||
val success = viewModel.testWebDav()
|
||||
showLoadingDialog = false
|
||||
if (success) {
|
||||
snackbarHostState.showSnackbar("WebDav 配置正确")
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.test_sync_status_success))
|
||||
} else {
|
||||
snackbarHostState.showSnackbar("WebDav 配置错误")
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.test_sync_status_fail))
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -483,7 +489,7 @@ fun BackupConfigScreen(
|
||||
) {
|
||||
OptionCard(
|
||||
icon = Icons.Default.PhoneAndroid,
|
||||
text = "本地恢复",
|
||||
text = stringResource(R.string.restore_from_local),
|
||||
onClick = {
|
||||
showRestoreOptionSheet = false
|
||||
restoreFileLauncher.launch(arrayOf("application/zip"))
|
||||
@@ -491,20 +497,23 @@ fun BackupConfigScreen(
|
||||
)
|
||||
OptionCard(
|
||||
icon = Icons.Default.Cloud,
|
||||
text = "网络恢复",
|
||||
text = stringResource(R.string.restore_from_network),
|
||||
onClick = {
|
||||
showRestoreOptionSheet = false
|
||||
scope.launch {
|
||||
showLoadingDialog = true
|
||||
loadingText = "加载中"
|
||||
loadingText = context.getString(R.string.loading)
|
||||
try {
|
||||
val names = viewModel.getBackupNames()
|
||||
backupNames = names
|
||||
showRestoreSheet = true
|
||||
} catch (e: Exception) {
|
||||
confirmDialogTitle = "恢复"
|
||||
confirmDialogTitle = context.getString(R.string.restore)
|
||||
confirmDialogText =
|
||||
"WebDavError\n${e.localizedMessage}\n将从本地备份恢复。"
|
||||
context.getString(
|
||||
R.string.webdav_restore_fallback_message,
|
||||
e.localizedMessage
|
||||
)
|
||||
onConfirmAction = {
|
||||
restoreFileLauncher.launch(arrayOf("application/zip"))
|
||||
}
|
||||
@@ -535,19 +544,21 @@ fun BackupConfigScreen(
|
||||
onToggleSelection = {
|
||||
showRestoreSheet = false
|
||||
showLoadingDialog = true
|
||||
loadingText = "恢复中…"
|
||||
loadingText = context.getString(R.string.on_restore)
|
||||
viewModel.restoreWebDav(
|
||||
it,
|
||||
{
|
||||
showLoadingDialog = false
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar("恢复成功")
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.restore_success))
|
||||
}
|
||||
},
|
||||
{ error ->
|
||||
showLoadingDialog = false
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar("WebDav恢复出错\n$error")
|
||||
snackbarHostState.showSnackbar(
|
||||
context.getString(R.string.webdav_restore_fail, error)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.legado.app.ui.config.backupConfig
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.legado.app.R
|
||||
import io.legado.app.domain.usecase.WebDavBackupUseCase
|
||||
import io.legado.app.help.storage.Backup
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -55,7 +56,7 @@ class BackupConfigViewModel(
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
onError(e.localizedMessage ?: "备份出错")
|
||||
onError(e.localizedMessage ?: appCtx.getString(R.string.backup_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +77,7 @@ class BackupConfigViewModel(
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
onError(e.localizedMessage ?: "恢复出错")
|
||||
onError(e.localizedMessage ?: appCtx.getString(R.string.restore_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,9 +134,11 @@ fun CoverConfigScreen(
|
||||
val coverCount = CoverConfig.defaultCover.split(",").filter { it.isNotBlank() }.size
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.default_cover),
|
||||
description = if (coverCount > 0) "已选择 $coverCount 张图片" else stringResource(
|
||||
R.string.select_image
|
||||
),
|
||||
description = if (coverCount > 0) {
|
||||
stringResource(R.string.cover_selected_images_count, coverCount)
|
||||
} else {
|
||||
stringResource(R.string.select_image)
|
||||
},
|
||||
onClick = { manageKey = PreferKey.defaultCover }
|
||||
)
|
||||
|
||||
@@ -191,9 +193,11 @@ fun CoverConfigScreen(
|
||||
CoverConfig.defaultCoverDark.split(",").filter { it.isNotBlank() }.size
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.default_cover),
|
||||
description = if (coverCount > 0) "已选择 $coverCount 张图片" else stringResource(
|
||||
R.string.select_image
|
||||
),
|
||||
description = if (coverCount > 0) {
|
||||
stringResource(R.string.cover_selected_images_count, coverCount)
|
||||
} else {
|
||||
stringResource(R.string.select_image)
|
||||
},
|
||||
onClick = { manageKey = PreferKey.defaultCoverDark }
|
||||
)
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ fun CoverRuleConfigSheet(
|
||||
imageVector = Icons.Default.Save,
|
||||
onClick = {
|
||||
if (searchUrl.isBlank() || coverRule.isBlank()) {
|
||||
appCtx.toastOnUi("搜索url和cover规则不能为空")
|
||||
appCtx.toastOnUi(R.string.cover_rule_fields_required)
|
||||
} else {
|
||||
val newConfig = ruleState?.copy(
|
||||
enable = enable,
|
||||
|
||||
@@ -72,7 +72,7 @@ fun CustomThemeScreen(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = "自定义颜色",
|
||||
title = stringResource(R.string.custom_theme_colors),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
TopBarNavigationButton(onClick = onBackClick)
|
||||
@@ -91,7 +91,7 @@ fun CustomThemeScreen(
|
||||
item {
|
||||
SplicedColumnGroup {
|
||||
SwitchSettingItem(
|
||||
title = "使用色板生成颜色",
|
||||
title = stringResource(R.string.theme_manage_use_palette_colors),
|
||||
checked = !enableDeepPersonalization,
|
||||
onCheckedChange = { ThemeConfig.enableDeepPersonalization = !it }
|
||||
)
|
||||
@@ -104,7 +104,7 @@ fun CustomThemeScreen(
|
||||
SplicedColumnGroup(title = stringResource(R.string.color_setting)) {
|
||||
// Primary colors
|
||||
ClickableSettingItem(
|
||||
title = "主题色",
|
||||
title = stringResource(R.string.theme_manage_primary_color),
|
||||
option = if (themeColor != 0) "#${Integer.toHexString(themeColor).uppercase()}" else stringResource(R.string.click_to_select),
|
||||
onClick = {
|
||||
currentColorKey = "themeColor"
|
||||
@@ -128,7 +128,7 @@ fun CustomThemeScreen(
|
||||
)
|
||||
|
||||
ClickableSettingItem(
|
||||
title = "次要主题色",
|
||||
title = stringResource(R.string.theme_manage_secondary_color),
|
||||
option = if (secondaryThemeColor != 0) "#${Integer.toHexString(secondaryThemeColor).uppercase()}" else stringResource(R.string.click_to_select),
|
||||
onClick = {
|
||||
currentColorKey = "secondaryThemeColor"
|
||||
@@ -152,7 +152,7 @@ fun CustomThemeScreen(
|
||||
)
|
||||
|
||||
ClickableSettingItem(
|
||||
title = "主要字体色",
|
||||
title = stringResource(R.string.theme_manage_primary_text_color),
|
||||
option = if (primaryTextColor != 0) "#${Integer.toHexString(primaryTextColor).uppercase()}" else stringResource(R.string.click_to_select),
|
||||
onClick = {
|
||||
currentColorKey = "primaryTextColor"
|
||||
@@ -176,7 +176,7 @@ fun CustomThemeScreen(
|
||||
)
|
||||
|
||||
ClickableSettingItem(
|
||||
title = "次要字体色",
|
||||
title = stringResource(R.string.theme_manage_secondary_text_color),
|
||||
option = if (secondaryTextColor != 0) "#${Integer.toHexString(secondaryTextColor).uppercase()}" else stringResource(R.string.click_to_select),
|
||||
onClick = {
|
||||
currentColorKey = "secondaryTextColor"
|
||||
@@ -200,7 +200,7 @@ fun CustomThemeScreen(
|
||||
)
|
||||
|
||||
ClickableSettingItem(
|
||||
title = "背景色",
|
||||
title = stringResource(R.string.theme_manage_background_color),
|
||||
option = if (themeBackgroundColor != 0) "#${Integer.toHexString(themeBackgroundColor).uppercase()}" else stringResource(R.string.click_to_select),
|
||||
onClick = {
|
||||
currentColorKey = "themeBackgroundColor"
|
||||
@@ -224,7 +224,7 @@ fun CustomThemeScreen(
|
||||
)
|
||||
|
||||
ClickableSettingItem(
|
||||
title = "标签容器色",
|
||||
title = stringResource(R.string.theme_manage_label_container_color),
|
||||
option = if (labelContainerColor != 0) "#${Integer.toHexString(labelContainerColor).uppercase()}" else stringResource(R.string.click_to_select),
|
||||
onClick = {
|
||||
currentColorKey = "labelContainerColor"
|
||||
|
||||
+3
-3
@@ -88,7 +88,7 @@ fun DirectLinkUploadBottomSheet(
|
||||
onDismissRequest = { showMenu = false }
|
||||
) {
|
||||
RoundDropdownMenuItem(
|
||||
text = "导入默认",
|
||||
text = stringResource(R.string.import_default_rule),
|
||||
leadingIcon = { Icon(Icons.Default.Download, null) },
|
||||
onClick = {
|
||||
showMenu = false
|
||||
@@ -124,7 +124,7 @@ fun DirectLinkUploadBottomSheet(
|
||||
viewModel.upView(rule)
|
||||
}
|
||||
}.onFailure {
|
||||
context.toastOnUi("剪贴板为空或格式不对")
|
||||
context.toastOnUi(R.string.clipboard_empty_or_invalid)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -186,7 +186,7 @@ fun DirectLinkUploadBottomSheet(
|
||||
if (viewModel.saveDirectLinkRule()) {
|
||||
onDismiss()
|
||||
} else {
|
||||
context.toastOnUi("请填写完整信息")
|
||||
context.toastOnUi(R.string.complete_required_information)
|
||||
}
|
||||
},
|
||||
dismissText = stringResource(R.string.cancel),
|
||||
|
||||
@@ -147,7 +147,7 @@ fun OtherConfigScreen(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
} else {
|
||||
Toast.makeText(context, "无需申请", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, R.string.permission_not_required, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -97,7 +97,7 @@ fun BackgroundImageManageSheet(
|
||||
) {
|
||||
AppIcon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = "Add",
|
||||
contentDescription = stringResource(R.string.add),
|
||||
modifier = Modifier.size(48.dp),
|
||||
tint = LegadoTheme.colorScheme.primary
|
||||
)
|
||||
|
||||
@@ -24,8 +24,10 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import io.legado.app.R
|
||||
import io.legado.app.help.config.TagColorGenerator
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.button.MediumOutlinedIconButton
|
||||
@@ -53,7 +55,7 @@ fun LabelColorManageSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = "管理标签颜色",
|
||||
title = stringResource(R.string.theme_config_manage_label_colors),
|
||||
startAction = {
|
||||
MediumOutlinedIconButton(
|
||||
onClick = {
|
||||
@@ -84,7 +86,7 @@ fun LabelColorManageSheet(
|
||||
) {
|
||||
items(tagColors.size) { index ->
|
||||
val colorPair = tagColors[index]
|
||||
val label = "标签 ${index + 1}"
|
||||
val label = stringResource(R.string.theme_config_label_color_name, index + 1)
|
||||
NormalCard(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
containerColor = LegadoTheme.colorScheme.onSheetContent
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.legado.app.ui.config.themeConfig
|
||||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -24,8 +25,10 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.AsyncImage
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.button.SmallTonalIconButton
|
||||
import io.legado.app.ui.widget.components.card.NormalCard
|
||||
@@ -36,7 +39,7 @@ import java.io.File
|
||||
|
||||
private data class NavIconDestination(
|
||||
val key: String,
|
||||
val label: String,
|
||||
@param:StringRes val labelRes: Int,
|
||||
val path: String,
|
||||
val onSetPath: (String) -> Unit,
|
||||
)
|
||||
@@ -51,10 +54,10 @@ fun NavIconManageSheet(
|
||||
var activeDest by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
val destinations = listOf(
|
||||
NavIconDestination("bookshelf", "书架", ThemeConfig.navIconBookshelf) { ThemeConfig.navIconBookshelf = it },
|
||||
NavIconDestination("explore", "发现", ThemeConfig.navIconExplore) { ThemeConfig.navIconExplore = it },
|
||||
NavIconDestination("rss", "订阅", ThemeConfig.navIconRss) { ThemeConfig.navIconRss = it },
|
||||
NavIconDestination("my", "我的", ThemeConfig.navIconMy) { ThemeConfig.navIconMy = it },
|
||||
NavIconDestination("bookshelf", R.string.bookshelf, ThemeConfig.navIconBookshelf) { ThemeConfig.navIconBookshelf = it },
|
||||
NavIconDestination("explore", R.string.discovery, ThemeConfig.navIconExplore) { ThemeConfig.navIconExplore = it },
|
||||
NavIconDestination("rss", R.string.rss, ThemeConfig.navIconRss) { ThemeConfig.navIconRss = it },
|
||||
NavIconDestination("my", R.string.my, ThemeConfig.navIconMy) { ThemeConfig.navIconMy = it },
|
||||
)
|
||||
|
||||
val selectImage = rememberLauncherForActivityResult(
|
||||
@@ -79,7 +82,7 @@ fun NavIconManageSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = "导航栏图标",
|
||||
title = stringResource(R.string.theme_config_nav_icons),
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(bottom = 24.dp)) {
|
||||
Row(
|
||||
@@ -89,6 +92,7 @@ fun NavIconManageSheet(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
destinations.forEach { dest ->
|
||||
val label = stringResource(dest.labelRes)
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
@@ -108,7 +112,7 @@ fun NavIconManageSheet(
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
AsyncImage(
|
||||
model = dest.path,
|
||||
contentDescription = dest.label,
|
||||
contentDescription = label,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Fit
|
||||
)
|
||||
@@ -128,7 +132,10 @@ fun NavIconManageSheet(
|
||||
) {
|
||||
AppIcon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = "添加${dest.label}图标",
|
||||
contentDescription = stringResource(
|
||||
R.string.theme_config_add_nav_icon,
|
||||
label
|
||||
),
|
||||
modifier = Modifier.size(32.dp),
|
||||
tint = LegadoTheme.colorScheme.primary
|
||||
)
|
||||
@@ -136,7 +143,7 @@ fun NavIconManageSheet(
|
||||
}
|
||||
}
|
||||
AppText(
|
||||
text = dest.label,
|
||||
text = label,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ fun ThemeConfigScreen(
|
||||
contentColor = MiuixTheme.colorScheme.onPrimary
|
||||
)
|
||||
) {
|
||||
MiuixText("Miuix 目前为测试主题,且不对基于View的界面生效!")
|
||||
MiuixText(stringResource(R.string.theme_config_miuix_experimental_warning))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,8 +420,8 @@ fun ThemeConfigScreen(
|
||||
onCheckedChange = { ThemeConfig.useFloatingBottomBarLiquidGlass = it }
|
||||
)
|
||||
SliderSettingItem(
|
||||
title = "底栏模糊强度",
|
||||
description = "控制液态玻璃的扭曲程度",
|
||||
title = stringResource(R.string.theme_config_bottom_bar_lens_radius),
|
||||
description = stringResource(R.string.theme_config_bottom_bar_lens_radius_summary),
|
||||
value = ThemeConfig.bottomBarLensRadius,
|
||||
defaultValue = 24f,
|
||||
valueRange = 0f..50f,
|
||||
@@ -483,30 +483,30 @@ fun ThemeConfigScreen(
|
||||
}
|
||||
if (ThemeConfig.enableBlur) {
|
||||
SliderSettingItem(
|
||||
title = "顶栏模糊半径",
|
||||
description = "模糊半径越大,系统运行越卡顿",
|
||||
title = stringResource(R.string.theme_manage_top_bar_blur_radius),
|
||||
description = stringResource(R.string.theme_config_blur_radius_performance_summary),
|
||||
value = ThemeConfig.topBarBlurRadius.toFloat(),
|
||||
defaultValue = 24f,
|
||||
valueRange = 0f..30f,
|
||||
onValueChange = { ThemeConfig.topBarBlurRadius = it.toInt() }
|
||||
)
|
||||
SliderSettingItem(
|
||||
title = "底栏模糊半径",
|
||||
description = "模糊半径越大,系统运行越卡顿",
|
||||
title = stringResource(R.string.theme_manage_bottom_bar_blur_radius),
|
||||
description = stringResource(R.string.theme_config_blur_radius_performance_summary),
|
||||
value = ThemeConfig.bottomBarBlurRadius.toFloat(),
|
||||
defaultValue = 8f,
|
||||
valueRange = 0f..10f,
|
||||
onValueChange = { ThemeConfig.bottomBarBlurRadius = it.toInt() }
|
||||
)
|
||||
SliderSettingItem(
|
||||
title = "顶栏模糊透明度",
|
||||
title = stringResource(R.string.theme_manage_top_bar_blur_opacity),
|
||||
value = ThemeConfig.topBarBlurAlpha.toFloat(),
|
||||
defaultValue = 73f,
|
||||
valueRange = 0f..100f,
|
||||
onValueChange = { ThemeConfig.topBarBlurAlpha = it.toInt() }
|
||||
)
|
||||
SliderSettingItem(
|
||||
title = "底栏模糊透明度",
|
||||
title = stringResource(R.string.theme_manage_bottom_bar_blur_opacity),
|
||||
value = ThemeConfig.bottomBarBlurAlpha.toFloat(),
|
||||
defaultValue = 40f,
|
||||
valueRange = 0f..100f,
|
||||
@@ -610,15 +610,15 @@ fun ThemeConfigScreen(
|
||||
|
||||
// Container settings
|
||||
item {
|
||||
SplicedColumnGroup(title = "容器设置") {
|
||||
SplicedColumnGroup(title = stringResource(R.string.theme_manage_section_container)) {
|
||||
SwitchSettingItem(
|
||||
title = "显示分割线",
|
||||
title = stringResource(R.string.show_divider_line),
|
||||
checked = ThemeConfig.enableItemDivider,
|
||||
onCheckedChange = { ThemeConfig.enableItemDivider = it }
|
||||
)
|
||||
if (ThemeConfig.enableItemDivider) {
|
||||
SliderSettingItem(
|
||||
title = "分割线粗细",
|
||||
title = stringResource(R.string.theme_config_divider_width),
|
||||
description = "${ThemeConfig.itemDividerWidth}dp",
|
||||
value = ThemeConfig.itemDividerWidth,
|
||||
defaultValue = 1f,
|
||||
@@ -627,7 +627,7 @@ fun ThemeConfigScreen(
|
||||
onValueChange = { ThemeConfig.itemDividerWidth = it }
|
||||
)
|
||||
SliderSettingItem(
|
||||
title = "分割线长度",
|
||||
title = stringResource(R.string.theme_config_divider_length),
|
||||
description = "${ThemeConfig.itemDividerLength.toInt()}%",
|
||||
value = ThemeConfig.itemDividerLength,
|
||||
defaultValue = 80f,
|
||||
@@ -636,7 +636,7 @@ fun ThemeConfigScreen(
|
||||
onValueChange = { ThemeConfig.itemDividerLength = it }
|
||||
)
|
||||
ClickableSettingItem(
|
||||
title = "分割线颜色",
|
||||
title = stringResource(R.string.tip_divider_color),
|
||||
option = if (ThemeConfig.itemDividerColor != 0) "#${Integer.toHexString(ThemeConfig.itemDividerColor).uppercase()}" else stringResource(R.string.click_to_select),
|
||||
onClick = {
|
||||
showBorderColorPicker = true
|
||||
@@ -659,7 +659,7 @@ fun ThemeConfigScreen(
|
||||
|
||||
// Nav icon settings
|
||||
item {
|
||||
SplicedColumnGroup(title = "导航栏图标设置") {
|
||||
SplicedColumnGroup(title = stringResource(R.string.theme_config_nav_icon_settings)) {
|
||||
val customCount = listOf(
|
||||
ThemeConfig.navIconBookshelf,
|
||||
ThemeConfig.navIconExplore,
|
||||
@@ -667,8 +667,12 @@ fun ThemeConfigScreen(
|
||||
ThemeConfig.navIconMy
|
||||
).count { it.isNotEmpty() }
|
||||
ClickableSettingItem(
|
||||
title = "导航栏图标",
|
||||
description = if (customCount > 0) "已设置 $customCount 个自定义图标" else "使用默认图标",
|
||||
title = stringResource(R.string.theme_config_nav_icons),
|
||||
description = if (customCount > 0) {
|
||||
stringResource(R.string.theme_config_nav_icons_custom_count, customCount)
|
||||
} else {
|
||||
stringResource(R.string.theme_config_nav_icons_default)
|
||||
},
|
||||
onClick = { showNavIconSheet = true }
|
||||
)
|
||||
}
|
||||
@@ -676,10 +680,10 @@ fun ThemeConfigScreen(
|
||||
|
||||
// Theme management
|
||||
item {
|
||||
SplicedColumnGroup(title = "主题管理") {
|
||||
SplicedColumnGroup(title = stringResource(R.string.theme_pack)) {
|
||||
ClickableSettingItem(
|
||||
title = "主题管理",
|
||||
description = "保存、导入、导出主题配置",
|
||||
title = stringResource(R.string.theme_pack),
|
||||
description = stringResource(R.string.theme_pack_s),
|
||||
onClick = onNavigateToThemeManage
|
||||
)
|
||||
}
|
||||
@@ -750,7 +754,7 @@ fun ThemeConfigScreen(
|
||||
startAction = {
|
||||
SmallIconButton(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = "清除",
|
||||
contentDescription = stringResource(R.string.clear),
|
||||
onClick = {
|
||||
ThemeConfig.appFontPath = null
|
||||
showFontSheet = false
|
||||
@@ -760,7 +764,7 @@ fun ThemeConfigScreen(
|
||||
endAction = {
|
||||
SmallIconButton(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = "选择文件夹",
|
||||
contentDescription = stringResource(R.string.select_folder),
|
||||
onClick = { fontFolderLauncher.launch(null) }
|
||||
)
|
||||
},
|
||||
@@ -771,7 +775,7 @@ fun ThemeConfigScreen(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "没有字体文件",
|
||||
text = stringResource(R.string.theme_config_no_font_files),
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
}
|
||||
|
||||
@@ -79,4 +79,4 @@ class ThemeConfigViewModel : ViewModel() {
|
||||
ThemeConfig.bgImageLight = newPath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.help.config.ThemeExportData
|
||||
@@ -58,7 +59,7 @@ fun EditThemeSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = "编辑主题",
|
||||
title = stringResource(R.string.theme_manage_edit_theme),
|
||||
endAction = {
|
||||
MediumIconButton(
|
||||
onClick = {
|
||||
@@ -92,30 +93,30 @@ fun EditThemeSheet(
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Basic settings
|
||||
SectionTitle("基础设置")
|
||||
SectionTitle(stringResource(R.string.theme_manage_section_basic))
|
||||
CompactDropdownSettingItem(
|
||||
title = "主题模式",
|
||||
title = stringResource(R.string.theme_mode),
|
||||
selectedValue = data.themeMode,
|
||||
displayEntries = stringArrayResource(R.array.theme_mode),
|
||||
entryValues = stringArrayResource(R.array.theme_mode_v),
|
||||
onValueChange = { data = data.copy(themeMode = it) }
|
||||
)
|
||||
CompactDropdownSettingItem(
|
||||
title = "调色板风格",
|
||||
title = stringResource(R.string.palette_style),
|
||||
selectedValue = data.paletteStyle,
|
||||
displayEntries = stringArrayResource(R.array.paletteStyle),
|
||||
entryValues = stringArrayResource(R.array.paletteStyle_value),
|
||||
onValueChange = { data = data.copy(paletteStyle = it) }
|
||||
)
|
||||
CompactDropdownSettingItem(
|
||||
title = "Material 版本",
|
||||
title = stringResource(R.string.material_version),
|
||||
selectedValue = data.materialVersion,
|
||||
displayEntries = stringArrayResource(R.array.materialVersion),
|
||||
entryValues = stringArrayResource(R.array.materialVersion_value),
|
||||
onValueChange = { data = data.copy(materialVersion = it) }
|
||||
)
|
||||
CompactDropdownSettingItem(
|
||||
title = "对比度偏好",
|
||||
title = stringResource(R.string.preferred_contrast),
|
||||
selectedValue = data.customContrast,
|
||||
displayEntries = stringArrayResource(R.array.customContrast),
|
||||
entryValues = stringArrayResource(R.array.customContrast_value),
|
||||
@@ -125,36 +126,36 @@ fun EditThemeSheet(
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Colors
|
||||
SectionTitle("颜色设置")
|
||||
SectionTitle(stringResource(R.string.theme_manage_section_colors))
|
||||
CompactSwitchSettingItem(
|
||||
title = "使用色板生成颜色",
|
||||
title = stringResource(R.string.theme_manage_use_palette_colors),
|
||||
checked = !data.enableDeepPersonalization,
|
||||
onCheckedChange = { data = data.copy(enableDeepPersonalization = !it) }
|
||||
)
|
||||
if (data.enableDeepPersonalization) {
|
||||
ColorItem("主题色", data.themeColor) {
|
||||
ColorItem(stringResource(R.string.theme_manage_primary_color), data.themeColor) {
|
||||
currentColorKey = "themeColor"; showColorPicker = true
|
||||
}
|
||||
ColorItem("次要主题色", data.secondaryThemeColor) {
|
||||
ColorItem(stringResource(R.string.theme_manage_secondary_color), data.secondaryThemeColor) {
|
||||
currentColorKey = "secondaryThemeColor"; showColorPicker = true
|
||||
}
|
||||
ColorItem("主要字体色", data.primaryTextColor) {
|
||||
ColorItem(stringResource(R.string.theme_manage_primary_text_color), data.primaryTextColor) {
|
||||
currentColorKey = "primaryTextColor"; showColorPicker = true
|
||||
}
|
||||
ColorItem("次要字体色", data.secondaryTextColor) {
|
||||
ColorItem(stringResource(R.string.theme_manage_secondary_text_color), data.secondaryTextColor) {
|
||||
currentColorKey = "secondaryTextColor"; showColorPicker = true
|
||||
}
|
||||
ColorItem("背景色", data.themeBackgroundColor) {
|
||||
ColorItem(stringResource(R.string.theme_manage_background_color), data.themeBackgroundColor) {
|
||||
currentColorKey = "themeBackgroundColor"; showColorPicker = true
|
||||
}
|
||||
ColorItem("标签容器色", data.labelContainerColor) {
|
||||
ColorItem(stringResource(R.string.theme_manage_label_container_color), data.labelContainerColor) {
|
||||
currentColorKey = "labelContainerColor"; showColorPicker = true
|
||||
}
|
||||
} else {
|
||||
ColorItem("日间种子色", data.cPrimary) {
|
||||
ColorItem(stringResource(R.string.theme_manage_day_seed_color), data.cPrimary) {
|
||||
currentColorKey = "cPrimary"; showColorPicker = true
|
||||
}
|
||||
ColorItem("夜间种子色", data.cNPrimary) {
|
||||
ColorItem(stringResource(R.string.theme_manage_night_seed_color), data.cNPrimary) {
|
||||
currentColorKey = "cNPrimary"; showColorPicker = true
|
||||
}
|
||||
}
|
||||
@@ -162,9 +163,9 @@ fun EditThemeSheet(
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Interface layout
|
||||
SectionTitle("界面布局")
|
||||
SectionTitle(stringResource(R.string.theme_manage_section_layout))
|
||||
CompactSwitchSettingItem(
|
||||
title = "发现",
|
||||
title = stringResource(R.string.theme_manage_show_discovery),
|
||||
checked = data.showDiscovery,
|
||||
onCheckedChange = { data = data.copy(showDiscovery = it) }
|
||||
)
|
||||
@@ -174,41 +175,41 @@ fun EditThemeSheet(
|
||||
onCheckedChange = { data = data.copy(showRss = it) }
|
||||
)
|
||||
CompactSwitchSettingItem(
|
||||
title = "显示底栏",
|
||||
title = stringResource(R.string.show_bottom_nav),
|
||||
checked = data.showBottomView,
|
||||
onCheckedChange = { data = data.copy(showBottomView = it) }
|
||||
)
|
||||
CompactSwitchSettingItem(
|
||||
title = "浮动底栏",
|
||||
title = stringResource(R.string.floating_bottom_bar),
|
||||
checked = data.useFloatingBottomBar,
|
||||
onCheckedChange = { data = data.copy(useFloatingBottomBar = it) }
|
||||
)
|
||||
CompactSwitchSettingItem(
|
||||
title = "状态栏",
|
||||
title = stringResource(R.string.theme_manage_status_bar),
|
||||
checked = data.showStatusBar,
|
||||
onCheckedChange = { data = data.copy(showStatusBar = it) }
|
||||
)
|
||||
CompactSwitchSettingItem(
|
||||
title = "翻页动画",
|
||||
title = stringResource(R.string.theme_manage_page_turn_animation),
|
||||
checked = data.swipeAnimation,
|
||||
onCheckedChange = { data = data.copy(swipeAnimation = it) }
|
||||
)
|
||||
CompactDropdownSettingItem(
|
||||
title = "平板模式",
|
||||
title = stringResource(R.string.tabletInterface),
|
||||
selectedValue = data.tabletInterface,
|
||||
displayEntries = stringArrayResource(R.array.tabletInterface),
|
||||
entryValues = stringArrayResource(R.array.tabletInterface_value),
|
||||
onValueChange = { data = data.copy(tabletInterface = it) }
|
||||
)
|
||||
CompactDropdownSettingItem(
|
||||
title = "标签显示",
|
||||
title = stringResource(R.string.theme_manage_label_visibility),
|
||||
selectedValue = data.labelVisibilityMode,
|
||||
displayEntries = stringArrayResource(R.array.label_vis_mode),
|
||||
entryValues = stringArrayResource(R.array.label_vis_mode_value),
|
||||
onValueChange = { data = data.copy(labelVisibilityMode = it) }
|
||||
)
|
||||
CompactDropdownSettingItem(
|
||||
title = "默认主页",
|
||||
title = stringResource(R.string.default_home_page),
|
||||
selectedValue = data.defaultHomePage,
|
||||
displayEntries = stringArrayResource(R.array.default_home_page),
|
||||
entryValues = stringArrayResource(R.array.default_home_page_value),
|
||||
@@ -218,33 +219,33 @@ fun EditThemeSheet(
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Blur
|
||||
SectionTitle("模糊效果")
|
||||
SectionTitle(stringResource(R.string.theme_manage_section_blur))
|
||||
CompactSwitchSettingItem(
|
||||
title = "启用模糊",
|
||||
title = stringResource(R.string.is_blur_enable),
|
||||
checked = data.enableBlur,
|
||||
onCheckedChange = { data = data.copy(enableBlur = it) }
|
||||
)
|
||||
if (data.enableBlur) {
|
||||
CompactSliderSettingItem(
|
||||
title = "顶栏模糊半径",
|
||||
title = stringResource(R.string.theme_manage_top_bar_blur_radius),
|
||||
value = data.topBarBlurRadius.toFloat(),
|
||||
valueRange = 1f..60f,
|
||||
onValueChange = { data = data.copy(topBarBlurRadius = it.toInt()) }
|
||||
)
|
||||
CompactSliderSettingItem(
|
||||
title = "底栏模糊半径",
|
||||
title = stringResource(R.string.theme_manage_bottom_bar_blur_radius),
|
||||
value = data.bottomBarBlurRadius.toFloat(),
|
||||
valueRange = 1f..60f,
|
||||
onValueChange = { data = data.copy(bottomBarBlurRadius = it.toInt()) }
|
||||
)
|
||||
CompactSliderSettingItem(
|
||||
title = "顶栏透明度",
|
||||
title = stringResource(R.string.theme_manage_top_bar_blur_opacity),
|
||||
value = data.topBarBlurAlpha.toFloat(),
|
||||
valueRange = 0f..255f,
|
||||
onValueChange = { data = data.copy(topBarBlurAlpha = it.toInt()) }
|
||||
)
|
||||
CompactSliderSettingItem(
|
||||
title = "底栏透明度",
|
||||
title = stringResource(R.string.theme_manage_bottom_bar_blur_opacity),
|
||||
value = data.bottomBarBlurAlpha.toFloat(),
|
||||
valueRange = 0f..255f,
|
||||
onValueChange = { data = data.copy(bottomBarBlurAlpha = it.toInt()) }
|
||||
@@ -254,15 +255,15 @@ fun EditThemeSheet(
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Opacity
|
||||
SectionTitle("透明度")
|
||||
SectionTitle(stringResource(R.string.theme_manage_section_opacity))
|
||||
CompactSliderSettingItem(
|
||||
title = "顶栏透明度",
|
||||
title = stringResource(R.string.top_bar_opacity),
|
||||
value = data.topBarOpacity.toFloat(),
|
||||
valueRange = 0f..100f,
|
||||
onValueChange = { data = data.copy(topBarOpacity = it.toInt()) }
|
||||
)
|
||||
CompactSliderSettingItem(
|
||||
title = "底栏透明度",
|
||||
title = stringResource(R.string.bottom_bar_opacity),
|
||||
value = data.bottomBarOpacity.toFloat(),
|
||||
valueRange = 0f..100f,
|
||||
onValueChange = { data = data.copy(bottomBarOpacity = it.toInt()) }
|
||||
@@ -271,9 +272,9 @@ fun EditThemeSheet(
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Container
|
||||
SectionTitle("容器设置")
|
||||
SectionTitle(stringResource(R.string.theme_manage_section_container))
|
||||
CompactSliderSettingItem(
|
||||
title = "容器不透明度",
|
||||
title = stringResource(R.string.container_opacity),
|
||||
value = data.containerOpacity.toFloat(),
|
||||
valueRange = 0f..100f,
|
||||
onValueChange = { data = data.copy(containerOpacity = it.toInt()) }
|
||||
@@ -282,14 +283,14 @@ fun EditThemeSheet(
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Other
|
||||
SectionTitle("其他")
|
||||
SectionTitle(stringResource(R.string.other))
|
||||
CompactSwitchSettingItem(
|
||||
title = "纯黑模式",
|
||||
title = stringResource(R.string.pure_black),
|
||||
checked = data.isPureBlack,
|
||||
onCheckedChange = { data = data.copy(isPureBlack = it) }
|
||||
)
|
||||
CompactSwitchSettingItem(
|
||||
title = "弹性顶栏",
|
||||
title = stringResource(R.string.use_flexible_top_bar),
|
||||
checked = data.useFlexibleTopAppBar,
|
||||
onCheckedChange = { data = data.copy(useFlexibleTopAppBar = it) }
|
||||
)
|
||||
|
||||
@@ -83,14 +83,14 @@ fun ThemeManageScreen(
|
||||
if (target != null) {
|
||||
exportTarget = null
|
||||
if (ThemeImportExport.exportSavedThemeToFile(context, target, it)) {
|
||||
context.toastOnUi("主题导出成功")
|
||||
context.toastOnUi(R.string.theme_manage_export_success)
|
||||
} else {
|
||||
context.toastOnUi("主题导出失败")
|
||||
context.toastOnUi(R.string.theme_manage_export_failed)
|
||||
}
|
||||
} else if (ThemeImportExport.exportToFile(context, it)) {
|
||||
context.toastOnUi("主题导出成功")
|
||||
context.toastOnUi(R.string.theme_manage_export_success)
|
||||
} else {
|
||||
context.toastOnUi("主题导出失败")
|
||||
context.toastOnUi(R.string.theme_manage_export_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,10 +100,10 @@ fun ThemeManageScreen(
|
||||
) { uri ->
|
||||
uri?.let {
|
||||
if (ThemeImportExport.importFromUri(context, it)) {
|
||||
context.toastOnUi("主题导入成功,部分设置需要重启生效")
|
||||
context.toastOnUi(R.string.theme_manage_import_success)
|
||||
showRestartDialog = true
|
||||
} else {
|
||||
context.toastOnUi("主题导入失败")
|
||||
context.toastOnUi(R.string.theme_manage_import_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,7 +112,7 @@ fun ThemeManageScreen(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
GlassMediumFlexibleTopAppBar(
|
||||
title = "主题管理",
|
||||
title = stringResource(R.string.theme_pack),
|
||||
scrollBehavior = scrollBehavior,
|
||||
navigationIcon = {
|
||||
TopBarNavigationButton(onClick = onBackClick)
|
||||
@@ -133,24 +133,24 @@ fun ThemeManageScreen(
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
SplicedColumnGroup {
|
||||
ClickableSettingItem(
|
||||
title = "保存当前设置",
|
||||
description = "保存当前主题配置为新主题",
|
||||
title = stringResource(R.string.theme_manage_save_current),
|
||||
description = stringResource(R.string.theme_manage_save_current_summary),
|
||||
onClick = {
|
||||
newThemeName = ""
|
||||
showSaveDialog = true
|
||||
}
|
||||
)
|
||||
ClickableSettingItem(
|
||||
title = "导出当前主题",
|
||||
description = "将当前主题配置导出为JSON文件",
|
||||
title = stringResource(R.string.theme_manage_export_current),
|
||||
description = stringResource(R.string.theme_manage_export_current_summary),
|
||||
onClick = {
|
||||
exportTarget = null
|
||||
exportLauncher.launch("legado_theme_${System.currentTimeMillis()}.json")
|
||||
}
|
||||
)
|
||||
ClickableSettingItem(
|
||||
title = "导入主题配置",
|
||||
description = "从JSON文件导入主题配置",
|
||||
title = stringResource(R.string.theme_manage_import_config),
|
||||
description = stringResource(R.string.theme_manage_import_config_summary),
|
||||
onClick = {
|
||||
importLauncher.launch(arrayOf("application/json"))
|
||||
}
|
||||
@@ -161,7 +161,7 @@ fun ThemeManageScreen(
|
||||
if (savedThemes.isNotEmpty()) {
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
AppText(
|
||||
text = "已保存的主题",
|
||||
text = stringResource(R.string.theme_manage_saved_themes),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 8.dp, bottom = 4.dp)
|
||||
@@ -207,8 +207,8 @@ fun ThemeManageScreen(
|
||||
AppAlertDialog(
|
||||
show = showSaveDialog,
|
||||
onDismissRequest = { showSaveDialog = false },
|
||||
title = "保存主题",
|
||||
confirmText = "保存",
|
||||
title = stringResource(R.string.theme_manage_save_theme),
|
||||
confirmText = stringResource(R.string.theme_manage_save),
|
||||
onConfirm = {
|
||||
if (newThemeName.isNotBlank()) {
|
||||
ThemeImportExport.saveCurrentAsTheme(newThemeName)
|
||||
@@ -216,13 +216,13 @@ fun ThemeManageScreen(
|
||||
showSaveDialog = false
|
||||
}
|
||||
},
|
||||
dismissText = "取消",
|
||||
dismissText = stringResource(R.string.cancel),
|
||||
onDismiss = { showSaveDialog = false },
|
||||
content = {
|
||||
AppTextField(
|
||||
value = newThemeName,
|
||||
onValueChange = { newThemeName = it },
|
||||
placeholder = { AppText(text = "请输入主题名称") },
|
||||
placeholder = { AppText(text = stringResource(R.string.theme_manage_name_hint)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
@@ -233,8 +233,8 @@ fun ThemeManageScreen(
|
||||
AppAlertDialog(
|
||||
show = applyTarget != null,
|
||||
onDismissRequest = { applyTarget = null },
|
||||
title = "应用主题",
|
||||
confirmText = "应用",
|
||||
title = stringResource(R.string.theme_manage_apply_theme),
|
||||
confirmText = stringResource(R.string.theme_manage_apply),
|
||||
onConfirm = {
|
||||
applyTarget?.let { theme ->
|
||||
ThemeImportExport.applySavedTheme(theme)
|
||||
@@ -242,17 +242,17 @@ fun ThemeManageScreen(
|
||||
}
|
||||
applyTarget = null
|
||||
},
|
||||
dismissText = "取消",
|
||||
dismissText = stringResource(R.string.cancel),
|
||||
onDismiss = { applyTarget = null },
|
||||
text = "确定应用主题「${applyTarget?.name}」?应用后需要重启才能完全生效。"
|
||||
text = stringResource(R.string.theme_manage_apply_message, applyTarget?.name.orEmpty())
|
||||
)
|
||||
|
||||
// Delete theme dialog
|
||||
AppAlertDialog(
|
||||
show = deleteTarget != null,
|
||||
onDismissRequest = { deleteTarget = null },
|
||||
title = "删除主题",
|
||||
confirmText = "删除",
|
||||
title = stringResource(R.string.theme_manage_delete_theme),
|
||||
confirmText = stringResource(R.string.delete),
|
||||
onConfirm = {
|
||||
deleteTarget?.let { theme ->
|
||||
ThemeImportExport.deleteSavedTheme(theme)
|
||||
@@ -260,9 +260,9 @@ fun ThemeManageScreen(
|
||||
}
|
||||
deleteTarget = null
|
||||
},
|
||||
dismissText = "取消",
|
||||
dismissText = stringResource(R.string.cancel),
|
||||
onDismiss = { deleteTarget = null },
|
||||
text = "确定删除主题「${deleteTarget?.name}」?此操作不可恢复。"
|
||||
text = stringResource(R.string.theme_manage_delete_message, deleteTarget?.name.orEmpty())
|
||||
)
|
||||
|
||||
// Edit theme sheet
|
||||
@@ -323,7 +323,7 @@ private fun SavedThemeItem(
|
||||
.background(lightBg)
|
||||
) {
|
||||
AppText(
|
||||
text = "日间",
|
||||
text = stringResource(R.string.theme_manage_preview_day),
|
||||
style = MaterialTheme.typography.labelMediumEmphasized,
|
||||
color = if (theme.data.primaryTextColor != 0) Color(theme.data.primaryTextColor).copy(alpha = 0.6f)
|
||||
else Color.Black.copy(alpha = 0.5f),
|
||||
@@ -355,7 +355,7 @@ private fun SavedThemeItem(
|
||||
.background(darkBg)
|
||||
) {
|
||||
AppText(
|
||||
text = "夜间",
|
||||
text = stringResource(R.string.theme_manage_preview_night),
|
||||
style = MaterialTheme.typography.labelMediumEmphasized,
|
||||
color = Color.White.copy(alpha = 0.5f),
|
||||
modifier = Modifier
|
||||
|
||||
@@ -41,7 +41,9 @@ import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
@@ -193,7 +195,7 @@ fun AppFloatingActionButtonMenu(
|
||||
tint = Color.White
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("菜单", color = Color.White)
|
||||
Text(stringResource(R.string.menu), color = Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.theme.LegadoTheme.composeEngine
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -57,10 +59,10 @@ fun AppPullToRefresh(
|
||||
contentPadding = PaddingValues(top = topPadding + 16.dp),
|
||||
pullToRefreshState = miuixRememberPullToRefreshState(),
|
||||
refreshTexts = listOf(
|
||||
"下拉刷新",
|
||||
"松开刷新",
|
||||
"正在刷新",
|
||||
"正在刷新"
|
||||
stringResource(R.string.pull_to_refresh),
|
||||
stringResource(R.string.release_to_refresh),
|
||||
stringResource(R.string.refreshing),
|
||||
stringResource(R.string.refreshing)
|
||||
)
|
||||
) {
|
||||
content()
|
||||
|
||||
@@ -15,9 +15,11 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.adaptiveHorizontalPadding
|
||||
import io.legado.app.ui.widget.components.card.GlassCard
|
||||
@@ -83,11 +85,15 @@ fun CollapsibleHeader(
|
||||
if (showIcon) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ExpandMore,
|
||||
contentDescription = if (isCollapsed) "展开" else "折叠",
|
||||
contentDescription = if (isCollapsed) {
|
||||
stringResource(R.string.expand)
|
||||
} else {
|
||||
stringResource(R.string.collapse)
|
||||
},
|
||||
modifier = Modifier.rotate(rotation),
|
||||
tint = LegadoTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ fun ColorPickerSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = "选择颜色",
|
||||
title = stringResource(R.string.select_color),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -123,7 +123,7 @@ fun ColorPickerSheet(
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = "色值",
|
||||
label = stringResource(R.string.color_value),
|
||||
singleLine = true,
|
||||
isError = isHexInputError,
|
||||
backgroundColor = LegadoTheme.colorScheme.surface,
|
||||
|
||||
Reference in New Issue
Block a user