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:
dat-bi
2026-05-22 01:56:08 +08:00
committed by Kudomaga
parent 43b401c52b
commit 7107b35728
34 changed files with 777 additions and 381 deletions
@@ -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"
@@ -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,