refactor: 统一FontSelectSheet组件

This commit is contained in:
HapeLee
2026-06-12 03:22:13 +08:00
parent d4745b55b4
commit 5ff9f3145a
6 changed files with 208 additions and 232 deletions
@@ -7,9 +7,14 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource
import androidx.core.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.data.repository.ReadPreferences
import io.legado.app.data.repository.ReadSettingsRepository
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.ui.book.read.sheet.BgTextConfigSheet
import io.legado.app.ui.book.read.sheet.ChangeChapterSourceSheet
import io.legado.app.ui.book.read.sheet.CharsetConfigSheet
@@ -18,7 +23,7 @@ import io.legado.app.ui.book.read.sheet.ContentEditSheet
import io.legado.app.ui.book.read.sheet.DictSheet
import io.legado.app.ui.book.read.sheet.DownloadSheet
import io.legado.app.ui.book.read.sheet.EffectiveReplacesSheet
import io.legado.app.ui.book.read.sheet.FontSelectSheet
import io.legado.app.ui.widget.components.FontSelectSheet
import io.legado.app.ui.book.read.sheet.HighlightRuleConfigSheet
import io.legado.app.ui.book.read.sheet.HttpTtsEditSheet
import io.legado.app.ui.book.read.sheet.MoreConfigSheet
@@ -59,70 +64,60 @@ fun ReadBookScreen(
}
// Dialogs driven by activeDialog state
when (val dialog = state.activeDialog) {
is ReadBookDialog.ConfirmRestoreProgress -> {
AppAlertDialog(
show = true,
onDismissRequest = { onIntent(ReadBookIntent.DismissDialog) },
title = stringResource(R.string.restore_progress),
text = stringResource(R.string.found_cloud_progress),
confirmText = stringResource(R.string.ok),
onConfirm = {
onIntent(ReadBookIntent.SureNewProgress(dialog.progress))
onIntent(ReadBookIntent.DismissDialog)
},
dismissText = stringResource(R.string.cancel),
onDismiss = { onIntent(ReadBookIntent.DismissDialog) },
)
}
val restoreDialog = state.activeDialog as? ReadBookDialog.ConfirmRestoreProgress
val syncDialog = state.activeDialog as? ReadBookDialog.SureSyncProgress
val skipDialog = state.activeDialog as? ReadBookDialog.ConfirmSkipToChapter
val payDialog = state.activeDialog as? ReadBookDialog.ConfirmChapterPay
is ReadBookDialog.SureSyncProgress -> {
AppAlertDialog(
show = true,
onDismissRequest = { onIntent(ReadBookIntent.DismissDialog) },
title = stringResource(R.string.sync_progress),
text = stringResource(R.string.progress_exceeds_cloud),
confirmText = stringResource(R.string.ok),
onConfirm = {
onIntent(ReadBookIntent.SureSyncProgress(dialog.progress))
onIntent(ReadBookIntent.DismissDialog)
},
dismissText = stringResource(R.string.cancel),
onDismiss = { onIntent(ReadBookIntent.DismissDialog) },
)
}
is ReadBookDialog.ConfirmSkipToChapter -> {
AppAlertDialog(
show = true,
onDismissRequest = { onIntent(ReadBookIntent.DismissDialog) },
title = stringResource(R.string.chapter_list),
text = stringResource(R.string.confirm_skip_to_chapter),
confirmText = stringResource(R.string.ok),
onConfirm = { onIntent(ReadBookIntent.DismissDialog) },
dismissText = stringResource(R.string.cancel),
onDismiss = { onIntent(ReadBookIntent.DismissDialog) },
)
}
is ReadBookDialog.ConfirmChapterPay -> {
AppAlertDialog(
show = true,
onDismissRequest = { onIntent(ReadBookIntent.DismissDialog) },
title = stringResource(R.string.chapter_pay),
text = dialog.chapterTitle,
confirmText = stringResource(R.string.ok),
onConfirm = {
onIntent(ReadBookIntent.DismissDialog)
onIntent(ReadBookIntent.ConfirmPayAction)
},
dismissText = stringResource(R.string.cancel),
onDismiss = { onIntent(ReadBookIntent.DismissDialog) },
)
}
null -> {}
}
AppAlertDialog(
show = restoreDialog != null,
onDismissRequest = { onIntent(ReadBookIntent.DismissDialog) },
title = stringResource(R.string.restore_progress),
text = stringResource(R.string.found_cloud_progress),
confirmText = stringResource(R.string.ok),
onConfirm = {
restoreDialog?.let { onIntent(ReadBookIntent.SureNewProgress(it.progress)) }
onIntent(ReadBookIntent.DismissDialog)
},
dismissText = stringResource(R.string.cancel),
onDismiss = { onIntent(ReadBookIntent.DismissDialog) },
)
AppAlertDialog(
show = syncDialog != null,
onDismissRequest = { onIntent(ReadBookIntent.DismissDialog) },
title = stringResource(R.string.sync_progress),
text = stringResource(R.string.progress_exceeds_cloud),
confirmText = stringResource(R.string.ok),
onConfirm = {
syncDialog?.let { onIntent(ReadBookIntent.SureSyncProgress(it.progress)) }
onIntent(ReadBookIntent.DismissDialog)
},
dismissText = stringResource(R.string.cancel),
onDismiss = { onIntent(ReadBookIntent.DismissDialog) },
)
AppAlertDialog(
show = skipDialog != null,
onDismissRequest = { onIntent(ReadBookIntent.DismissDialog) },
title = stringResource(R.string.chapter_list),
text = stringResource(R.string.confirm_skip_to_chapter),
confirmText = stringResource(R.string.ok),
onConfirm = { onIntent(ReadBookIntent.DismissDialog) },
dismissText = stringResource(R.string.cancel),
onDismiss = { onIntent(ReadBookIntent.DismissDialog) },
)
AppAlertDialog(
show = payDialog != null,
onDismissRequest = { onIntent(ReadBookIntent.DismissDialog) },
title = stringResource(R.string.chapter_pay),
text = payDialog?.chapterTitle ?: "",
confirmText = stringResource(R.string.ok),
onConfirm = {
onIntent(ReadBookIntent.DismissDialog)
onIntent(ReadBookIntent.ConfirmPayAction)
},
dismissText = stringResource(R.string.cancel),
onDismiss = { onIntent(ReadBookIntent.DismissDialog) },
)
// AppModalBottomSheet-based sheets — always composed, controlled by show flag
// for proper enter/exit animations
@@ -146,12 +141,23 @@ fun ReadBookScreen(
onDismissRequest = dismissSheet,
onIntent = onIntent,
)
val fontSelectReadSettings: ReadSettingsRepository = org.koin.compose.koinInject()
val fontSelectPreferences by fontSelectReadSettings.preferences.collectAsStateWithLifecycle(
initialValue = ReadPreferences()
)
val fontSelectFolderUri = fontSelectPreferences.fontFolder
.takeIf { it.isNotEmpty() }?.toUri()
val fontSelectSystemTypefaces = stringArrayResource(R.array.system_typefaces)
FontSelectSheet(
show = state.activeSheet is ReadBookSheet.FontSelect,
title = stringResource(R.string.select_font),
fontFolderUri = fontSelectFolderUri,
selectedFontPath = ReadBookConfig.textFont,
onDismissRequest = dismissSheet,
onSelectFont = { onIntent(ReadBookIntent.SelectFont(it)) },
onSelectFont = { onIntent(ReadBookIntent.SelectFont(it.uri.toString())) },
onSelectSystemTypeface = { onIntent(ReadBookIntent.SelectSystemTypeface(it)) },
onOpenFolderPicker = { onIntent(ReadBookIntent.OpenFontFolderPicker) },
systemTypefaces = fontSelectSystemTypefaces,
)
ToolButtonConfigSheet(
show = state.activeSheet is ReadBookSheet.ToolButtonConfig,
@@ -392,12 +398,17 @@ fun ReadBookScreen(
}
is ReadBookSheet.ChangeBookSource -> {
val sheet = state.activeSheet
val book = state.book
if (book != null) {
val changeSourceSheet = state.activeSheet as? ReadBookSheet.ChangeBookSource
val changeSourceBook = state.book
if (changeSourceSheet != null && changeSourceBook == null) {
LaunchedEffect(changeSourceSheet) {
onIntent(ReadBookIntent.DismissSheet)
}
}
if (changeSourceBook != null) {
ChangeSourceSheet(
show = true,
oldBook = book,
show = changeSourceSheet != null,
oldBook = changeSourceBook,
fromReadBookActivity = true,
allowAddAsNew = false,
dismissOnReplaceStart = true,
@@ -414,10 +425,6 @@ fun ReadBookScreen(
onIntent(ReadBookIntent.AddSourceAsNewBook(newBook, toc))
},
)
} else {
LaunchedEffect(sheet) {
onIntent(ReadBookIntent.DismissSheet)
}
}
}
@@ -1,54 +0,0 @@
package io.legado.app.ui.book.read.sheet
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.core.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.data.repository.ReadPreferences
import io.legado.app.data.repository.ReadSettingsRepository
import io.legado.app.help.config.ReadBookConfig
import org.koin.compose.koinInject
import java.io.File
import java.net.URLDecoder
import io.legado.app.ui.widget.components.FontSelectSheet as SharedFontSelectSheet
@Composable
fun FontSelectSheet(
show: Boolean,
onDismissRequest: () -> Unit,
onSelectFont: (String) -> Unit,
onSelectSystemTypeface: (Int) -> Unit,
onOpenFolderPicker: () -> Unit,
) {
val context = LocalContext.current
val readSettingsRepository: ReadSettingsRepository = koinInject()
val preferences by readSettingsRepository.preferences.collectAsStateWithLifecycle(
initialValue = ReadPreferences()
)
val fontFolderUri = preferences.fontFolder.takeIf { it.isNotEmpty() }?.toUri()
val curFontPath = remember { ReadBookConfig.textFont }
val curName = remember {
runCatching {
URLDecoder.decode(curFontPath, "utf-8")
}.getOrNull()?.substringAfterLast(File.separator)
}
val systemTypefaces = remember {
context.resources.getStringArray(R.array.system_typefaces)
}
SharedFontSelectSheet(
show = show,
title = stringResource(R.string.select_font),
fontFolderUri = fontFolderUri,
selectedFontName = curName,
onDismissRequest = onDismissRequest,
onSelectFont = { doc -> onSelectFont(doc.toString()) },
onOpenFolderPicker = onOpenFolderPicker,
onSelectSystemTypeface = onSelectSystemTypeface,
systemTypefaces = systemTypefaces,
)
}
@@ -28,11 +28,18 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.res.stringArrayResource
import androidx.core.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.data.repository.ReadPreferences
import io.legado.app.data.repository.ReadSettingsRepository
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.ui.book.read.ConfigUpdate
import io.legado.app.ui.book.read.ReadBookIntent
import io.legado.app.ui.widget.components.FontSelectSheet
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
import org.koin.compose.koinInject
import io.legado.app.ui.widget.components.settingItem.TinyClickableSettingItem
import io.legado.app.ui.widget.components.settingItem.TinyColorSettingItem
import io.legado.app.ui.widget.components.settingItem.TinyDropdownSettingItem
@@ -367,48 +374,55 @@ internal fun HeaderFooterPage(
}
// Color picker
if (showColorPicker) {
ColorPickerSheet(
show = true,
initialColor = colorPickerInitial,
onDismissRequest = { showColorPicker = false },
onColorSelected = { color ->
when (colorPickerId) {
COLOR_HEADER -> {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.TipHeaderColor(color)))
}
COLOR_FOOTER -> {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.TipFooterColor(color)))
}
COLOR_DIVIDER -> {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.TipDividerColor(color)))
}
ColorPickerSheet(
show = showColorPicker,
initialColor = colorPickerInitial,
onDismissRequest = { showColorPicker = false },
onColorSelected = { color ->
when (colorPickerId) {
COLOR_HEADER -> {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.TipHeaderColor(color)))
}
showColorPicker = false
},
)
}
COLOR_FOOTER -> {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.TipFooterColor(color)))
}
COLOR_DIVIDER -> {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.TipDividerColor(color)))
}
}
showColorPicker = false
},
)
// Font selector for header/footer
if (showFontSelect) {
FontSelectSheet(
show = true,
onDismissRequest = { showFontSelect = false },
onSelectFont = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.HeaderFont(it)))
onIntent(ReadBookIntent.SaveReadStyleConfig)
showFontSelect = false
},
onSelectSystemTypeface = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.HeaderFont("")))
onIntent(ReadBookIntent.SaveReadStyleConfig)
showFontSelect = false
},
onOpenFolderPicker = { /* handled by FontSelectSheet internally */ },
)
}
val readSettingsRepository: ReadSettingsRepository = koinInject()
val preferences by readSettingsRepository.preferences.collectAsStateWithLifecycle(
initialValue = ReadPreferences()
)
val fontFolderUri = preferences.fontFolder.takeIf { it.isNotEmpty() }?.toUri()
val systemTypefaces = stringArrayResource(R.array.system_typefaces)
FontSelectSheet(
show = showFontSelect,
title = stringResource(R.string.select_font),
fontFolderUri = fontFolderUri,
selectedFontPath = ReadBookConfig.headerFont,
onDismissRequest = { showFontSelect = false },
onSelectFont = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.HeaderFont(it.uri.toString())))
onIntent(ReadBookIntent.SaveReadStyleConfig)
showFontSelect = false
},
onSelectSystemTypeface = {
onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.HeaderFont("")))
onIntent(ReadBookIntent.SaveReadStyleConfig)
showFontSelect = false
},
onOpenFolderPicker = { /* handled by FontSelectSheet internally */ },
systemTypefaces = systemTypefaces,
)
}
@Composable
@@ -38,13 +38,19 @@ import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.res.stringArrayResource
import androidx.core.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.data.entities.HighlightRule
import io.legado.app.data.repository.ReadPreferences
import io.legado.app.data.repository.ReadSettingsRepository
import io.legado.app.data.repository.configNames
import io.legado.app.data.repository.toJsonArray
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.FontSelectSheet
import io.legado.app.ui.widget.components.SectionTitle
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
@@ -463,65 +469,65 @@ fun HighlightRuleEditSheet(
}
// Color pickers
if (showTextColorPicker) {
ColorPickerSheet(
show = true,
initialColor = textColor,
onDismissRequest = { showTextColorPicker = false },
onColorSelected = { color ->
textColor = color
showTextColorPicker = false
},
)
}
if (showBgColorPicker) {
ColorPickerSheet(
show = true,
initialColor = bgColor,
onDismissRequest = { showBgColorPicker = false },
onColorSelected = { color ->
bgColor = color
showBgColorPicker = false
},
)
}
if (showUnderlineColorPicker) {
ColorPickerSheet(
show = true,
initialColor = underlineColor,
onDismissRequest = { showUnderlineColorPicker = false },
onColorSelected = { color ->
underlineColor = color
showUnderlineColorPicker = false
},
)
}
ColorPickerSheet(
show = showTextColorPicker,
initialColor = textColor,
onDismissRequest = { showTextColorPicker = false },
onColorSelected = { color ->
textColor = color
showTextColorPicker = false
},
)
ColorPickerSheet(
show = showBgColorPicker,
initialColor = bgColor,
onDismissRequest = { showBgColorPicker = false },
onColorSelected = { color ->
bgColor = color
showBgColorPicker = false
},
)
ColorPickerSheet(
show = showUnderlineColorPicker,
initialColor = underlineColor,
onDismissRequest = { showUnderlineColorPicker = false },
onColorSelected = { color ->
underlineColor = color
showUnderlineColorPicker = false
},
)
// Font selector
if (showFontSelect) {
val readSettingsRepository: io.legado.app.data.repository.ReadSettingsRepository =
org.koin.compose.koinInject()
val scope = rememberCoroutineScope()
val fontFolderLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.OpenDocumentTree()
) { uri ->
uri?.let {
context.contentResolver.takePersistableUriPermission(
it, Intent.FLAG_GRANT_READ_URI_PERMISSION
)
scope.launch {
readSettingsRepository.setFontFolder(it.toString())
}
val readSettingsRepository: ReadSettingsRepository = org.koin.compose.koinInject()
val fontSelectScope = rememberCoroutineScope()
val fontSelectPreferences by readSettingsRepository.preferences.collectAsStateWithLifecycle(
initialValue = ReadPreferences()
)
val fontFolderUri = fontSelectPreferences.fontFolder.takeIf { it.isNotEmpty() }?.toUri()
val systemTypefaces = stringArrayResource(R.array.system_typefaces)
val fontFolderLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.OpenDocumentTree()
) { uri ->
uri?.let {
context.contentResolver.takePersistableUriPermission(
it, Intent.FLAG_GRANT_READ_URI_PERMISSION
)
fontSelectScope.launch {
readSettingsRepository.setFontFolder(it.toString())
}
}
FontSelectSheet(
show = true,
onDismissRequest = { showFontSelect = false },
onSelectFont = { fontPath = it; showFontSelect = false },
onSelectSystemTypeface = { fontPath = ""; showFontSelect = false },
onOpenFolderPicker = { fontFolderLauncher.launch(null) },
)
}
FontSelectSheet(
show = showFontSelect,
title = stringResource(R.string.select_font),
fontFolderUri = fontFolderUri,
selectedFontPath = fontPath,
onDismissRequest = { showFontSelect = false },
onSelectFont = { fontPath = it.uri.toString(); showFontSelect = false },
onSelectSystemTypeface = { fontPath = ""; showFontSelect = false },
onOpenFolderPicker = { fontFolderLauncher.launch(null) },
systemTypefaces = systemTypefaces,
)
}
@Composable
@@ -72,7 +72,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.base.AppContextWrapper
@@ -868,19 +868,11 @@ fun ThemeConfigScreen(
}
)
val curName = remember(ThemeConfig.appFontPath) {
ThemeConfig.appFontPath?.let { uri ->
runCatching {
DocumentFile.fromSingleUri(context, uri.toUri())?.name
}.getOrNull()
}
}
FontSelectSheet(
show = showFontSheet,
title = stringResource(R.string.font_setting),
fontFolderUri = fontFolderUri,
selectedFontName = curName,
selectedFontPath = ThemeConfig.appFontPath,
onDismissRequest = { showFontSheet = false },
onSelectFont = { doc ->
ThemeConfig.appFontPath = doc.uri.toString()
@@ -13,6 +13,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.utils.FileDoc
@@ -22,7 +25,7 @@ fun FontSelectSheet(
show: Boolean = true,
title: String,
fontFolderUri: Uri?,
selectedFontName: String?,
selectedFontPath: String?,
onDismissRequest: () -> Unit,
onSelectFont: (FileDoc) -> Unit,
onOpenFolderPicker: () -> Unit,
@@ -33,6 +36,14 @@ fun FontSelectSheet(
systemTypefaces: Array<String>? = null,
emptyText: String? = null,
) {
val context = LocalContext.current
val selectedFontName = remember(selectedFontPath) {
selectedFontPath?.let {
runCatching {
DocumentFile.fromSingleUri(context, it.toUri())?.name
}.getOrNull()
}
}
var showTypefaceMenu by remember { mutableStateOf(false) }
AppModalBottomSheet(