From 1d47e29a1810616c2df504c94e3f78f3683d2abf Mon Sep 17 00:00:00 2001 From: HapeLee <63206378+HapeLee@users.noreply.github.com> Date: Tue, 9 Jun 2026 21:58:07 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=9C=97=E8=AF=BB=E5=BC=95=E6=93=8E?= =?UTF-8?q?=E9=95=BF=E6=8C=89=E4=BA=8B=E4=BB=B6=E4=B8=8E=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/book/read/ReadBookContract.kt | 6 ++ .../app/ui/book/read/ReadBookController.kt | 1 + .../app/ui/book/read/ReadBookRouteScreen.kt | 8 +++ .../app/ui/book/read/ReadBookViewModel.kt | 66 ++++++++++++++++++- .../book/read/sheet/ReadAloudConfigSheet.kt | 40 ++++++----- .../settingItem/TinySettingItems.kt | 4 ++ app/src/main/res/values-zh-rCN/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 8 files changed, 111 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt index a9b19830e..b17522274 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt @@ -191,6 +191,7 @@ data class ReadMenuConfig( data class ReadBookTtsEngineItem( val title: String, val value: String?, + val loginUrl: String? = null, ) @Immutable @@ -308,6 +309,8 @@ sealed interface ReadBookIntent { data class OpenSourceEditByUrl(val sourceUrl: String) : ReadBookIntent data object OpenBookInfo : ReadBookIntent data object OpenChapterList : ReadBookIntent + data object OpenChapterUrl : ReadBookIntent + data class SetReadUrlInBrowser(val useBrowser: Boolean) : ReadBookIntent // Content edit data object LoadContentEdit : ReadBookIntent @@ -422,6 +425,7 @@ sealed interface ReadBookIntent { data class DeleteHttpTts(val engineId: Long) : ReadBookIntent data class SaveHttpTts(val httpTTS: HttpTTS) : ReadBookIntent data class ApplySpeakEnginePerBook(val value: String?) : ReadBookIntent + data class OpenHttpTtsLogin(val engineId: Long) : ReadBookIntent data class ImportHttpTtsJson(val json: String) : ReadBookIntent data object ExportAllHttpTts : ReadBookIntent data class SetReadAloudIgnoreAudioFocus(val value: Boolean) : ReadBookIntent @@ -558,6 +562,7 @@ sealed interface ReadBookEffect { data class OpenMenuCustomIconPicker(val id: String) : ReadBookEffect data class OpenTitleBarCustomIconPicker(val id: String) : ReadBookEffect data object OpenSystemTtsSettings : ReadBookEffect + data class OpenHttpTtsLogin(val engineId: Long) : ReadBookEffect // Day/night toggle data object ToggleDayNight : ReadBookEffect @@ -626,6 +631,7 @@ sealed interface ReadBookDialog { data class SureSyncProgress(val progress: BookProgress) : ReadBookDialog data object ConfirmSkipToChapter : ReadBookDialog data class ConfirmChapterPay(val chapterTitle: String) : ReadBookDialog + data object ConfirmReadUrlInBrowser : ReadBookDialog } /** diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt index 8e86d6218..e66625e9f 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt @@ -771,6 +771,7 @@ class ReadBookController( is ReadBookEffect.OpenMenuCustomIconPicker, is ReadBookEffect.OpenTitleBarCustomIconPicker, is ReadBookEffect.OpenSystemTtsSettings, + is ReadBookEffect.OpenHttpTtsLogin, is ReadBookEffect.TtsCacheCleared, is ReadBookEffect.ExportJson, // DB query + bookmark effects — handled by ViewModel, ignored here diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt index d3bb019df..184c4bff1 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt @@ -262,6 +262,14 @@ fun ReadBookRouteScreen( } ) } + is ReadBookEffect.OpenHttpTtsLogin -> { + context.startActivity( + Intent(context, SourceLoginActivity::class.java).apply { + putExtra("type", "httpTts") + putExtra("key", effect.engineId.toString()) + } + ) + } is ReadBookEffect.OpenWebView -> { context.startActivity( Intent(context, WebViewActivity::class.java).apply { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index f624f3a3c..838f78b9f 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -39,7 +39,10 @@ import io.legado.app.help.book.isLocalTxt import io.legado.app.help.book.isMobi import io.legado.app.help.book.removeType import io.legado.app.help.book.simulatedTotalChapterNum +import io.legado.app.constant.AppPattern import io.legado.app.help.config.AppConfig +import io.legado.app.utils.EncoderUtils +import io.legado.app.utils.openUrl import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.source.getSourceType @@ -338,6 +341,11 @@ class ReadBookViewModel( _effects.tryEmit(ReadBookEffect.OpenChapterList(bookUrl)) } } + is ReadBookIntent.OpenChapterUrl -> openChapterUrl() + is ReadBookIntent.SetReadUrlInBrowser -> { + AppConfig.readUrlInBrowser = intent.useBrowser + openChapterUrl() + } is ReadBookIntent.LoadContentEdit -> loadContentEdit() is ReadBookIntent.SaveContentEdit -> saveContentEdit(intent.content, intent.saveToSource) is ReadBookIntent.ResetContentEdit -> resetContentEdit() @@ -562,6 +570,7 @@ class ReadBookViewModel( } is ReadBookIntent.ApplySpeakEngine -> { + ReadBook.book?.setTtsEngine(null) AppConfig.ttsEngine = intent.value _uiState.update { it.copy( @@ -653,6 +662,10 @@ class ReadBookViewModel( } } + is ReadBookIntent.OpenHttpTtsLogin -> { + _effects.tryEmit(ReadBookEffect.OpenHttpTtsLogin(intent.engineId)) + } + is ReadBookIntent.ImportHttpTtsJson -> { execute { HttpTTS.fromJsonArray(intent.json).getOrDefault(arrayListOf()) @@ -976,7 +989,13 @@ class ReadBookViewModel( buildList { add(ReadBookTtsEngineItem(context.getString(R.string.system_tts), null)) appDb.httpTTSDao.all.forEach { httpTts -> - add(ReadBookTtsEngineItem(httpTts.name, httpTts.id.toString())) + add( + ReadBookTtsEngineItem( + title = httpTts.name, + value = httpTts.id.toString(), + loginUrl = httpTts.loginUrl, + ) + ) } } }.onSuccess { items -> @@ -2947,6 +2966,51 @@ class ReadBookViewModel( } } + private fun openChapterUrl() { + val book = ReadBook.book ?: return + if (book.isLocal) return + val chapter = ReadBook.curTextChapter?.chapter + ?: appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex) + ?: return + val rawUrl = chapter.getAbsoluteURL() + if (rawUrl.isBlank()) return + val url = resolveChapterUrl(rawUrl) + if (url.isBlank()) { + context.toastOnUi(R.string.cannot_open_url) + return + } + if (AppConfig.readUrlInBrowser) { + context.openUrl(url.substringBefore(",{")) + } else { + _effects.tryEmit( + ReadBookEffect.OpenWebView( + title = chapter.title, + url = url, + sourceOrigin = ReadBook.bookSource?.bookSourceUrl, + sourceName = ReadBook.bookSource?.bookSourceName, + sourceType = ReadBook.bookSource?.getSourceType(), + ) + ) + } + } + + /** + * 解析章节URL,处理data:;base64,...格式 + * 从base64编码的JSON中提取真实URL + */ + private fun resolveChapterUrl(url: String): String { + if (!url.startsWith("data:", ignoreCase = true)) return url + val match = AppPattern.dataUriRegex.matchEntire(url) ?: return url + return try { + val decoded = EncoderUtils.base64Decode(match.groupValues[1]) + val json = GSON.fromJsonObject>(decoded).getOrNull() + val realUrl = json?.get("url")?.toString() ?: "" + realUrl.ifBlank { url } + } catch (_: Exception) { + url + } + } + private fun showPayDialog() { val book = ReadBook.book ?: return if (book.isLocal) return diff --git a/app/src/main/java/io/legado/app/ui/book/read/sheet/ReadAloudConfigSheet.kt b/app/src/main/java/io/legado/app/ui/book/read/sheet/ReadAloudConfigSheet.kt index 3d2d8a584..a7a1908f0 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/sheet/ReadAloudConfigSheet.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/sheet/ReadAloudConfigSheet.kt @@ -43,6 +43,7 @@ import io.legado.app.ui.book.read.ReadBookIntent import io.legado.app.ui.book.read.ReadBookTtsEngineItem import io.legado.app.ui.book.read.ReadBookUiState import io.legado.app.ui.widget.components.AppTextField +import io.legado.app.ui.widget.components.alert.AppAlertDialog import io.legado.app.ui.widget.components.button.series.SmallTonalButton import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem @@ -164,6 +165,24 @@ fun SpeakEngineConfigSheet( val clipboardManager = LocalClipboardManager.current val items = state.ttsEngineItems val selectedValue = state.selectedTtsEngine + var pendingEngineValue by remember { mutableStateOf(null) } + + AppAlertDialog( + show = pendingEngineValue != null, + onDismissRequest = { pendingEngineValue = null }, + title = stringResource(R.string.speak_engine), + text = stringResource(R.string.speak_engine_apply_scope), + confirmText = stringResource(R.string.general), + onConfirm = { + onIntent(ReadBookIntent.ApplySpeakEngine(pendingEngineValue)) + pendingEngineValue = null + }, + dismissText = stringResource(R.string.book), + onDismiss = { + onIntent(ReadBookIntent.ApplySpeakEnginePerBook(pendingEngineValue)) + pendingEngineValue = null + }, + ) AppModalBottomSheet( show = show, @@ -177,7 +196,6 @@ fun SpeakEngineConfigSheet( onClick = { onIntent(ReadBookIntent.EditHttpTts()) }, icon = Icons.Default.Add ) - //TODO: 导入 SmallTonalButton( onClick = { clipboardManager.getText()?.text?.let { text -> @@ -194,20 +212,6 @@ fun SpeakEngineConfigSheet( ) } }, - endAction = { - Row( - horizontalArrangement = Arrangement.spacedBy(2.dp) - ) { - SmallTonalButton( - onClick = { onIntent(ReadBookIntent.ApplySpeakEnginePerBook(selectedValue)) }, - text = stringResource(R.string.book) - ) - SmallTonalButton( - onClick = { onIntent(ReadBookIntent.ApplySpeakEngine(selectedValue)) }, - text = stringResource(R.string.general) - ) - } - } ) { LazyColumn( modifier = Modifier @@ -225,7 +229,10 @@ fun SpeakEngineConfigSheet( } else { null }, - onClick = { onIntent(ReadBookIntent.ApplySpeakEngine(item.value)) }, + onClick = { pendingEngineValue = item.value }, + onLongClick = if (isHttpTts && !item.loginUrl.isNullOrBlank()) { + { onIntent(ReadBookIntent.OpenHttpTtsLogin(item.value!!.toLong())) } + } else null, trailingContent = if (isHttpTts) { { Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) { @@ -243,7 +250,6 @@ fun SpeakEngineConfigSheet( ) } } - } } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/TinySettingItems.kt b/app/src/main/java/io/legado/app/ui/widget/components/settingItem/TinySettingItems.kt index ed0e845d4..fb8c451f8 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/settingItem/TinySettingItems.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/settingItem/TinySettingItems.kt @@ -75,6 +75,7 @@ fun TinySettingItem( expandContent: (@Composable ColumnScope.() -> Unit)? = null, enabled: Boolean = true, onClick: (() -> Unit)? = null, + onLongClick: (() -> Unit)? = null, ) { val isExpandable = expandContent != null && onExpandChange != null val alpha = if (enabled) 1f else 0.5f @@ -88,6 +89,7 @@ fun TinySettingItem( } } } else null, + onLongClick = if (enabled) onLongClick else null, modifier = modifier .padding(bottom = 4.dp) .heightIn(min = 56.dp) @@ -329,6 +331,7 @@ fun TinyClickableSettingItem( color: Color? = LegadoTheme.colorScheme.surfaceContainerLow, trailingContent: (@Composable () -> Unit)? = null, onClick: () -> Unit, + onLongClick: (() -> Unit)? = null, ) { TinySettingItem( title = title, @@ -345,6 +348,7 @@ fun TinyClickableSettingItem( ) }, onClick = onClick, + onLongClick = onLongClick, ) } diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index fc7742545..99e6932d8 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -649,6 +649,7 @@ 自动亮度 %s 按页朗读 朗读引擎 + 选择朗读引擎的应用范围 背景图片 背景颜色 使用背景图片 @@ -1072,6 +1073,7 @@ 全屏 打开方式 是否使用外部浏览器打开? + 该URL无法直接打开 查看 打开 删除登录头 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c9f749024..36d6626b2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -670,6 +670,7 @@ Auto-Brightness %s Speak by pages Speak Engine + Choose where to apply the selected speak engine Background Images Background color Use Background Image @@ -1101,6 +1102,7 @@ full screen open function Is it opened in an external browser? + This URL cannot be opened directly see open del login header