├── .github
├── scripts
│ └── lzy_web.py
└── workflows
│ ├── automerge.yml
│ ├── debug.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── README.md
├── app
├── .gitignore
├── build.gradle
├── libs
│ ├── README.md
│ └── rhino-1.7.13-1.jar
├── proguard-rules.pro
├── schemas
│ └── com.github.jing332.tts_server_android.data.AppDatabase
│ │ ├── 1.json
│ │ ├── 10.json
│ │ ├── 11.json
│ │ ├── 12.json
│ │ ├── 13.json
│ │ ├── 14.json
│ │ ├── 15.json
│ │ ├── 16.json
│ │ ├── 17.json
│ │ ├── 18.json
│ │ ├── 19.json
│ │ ├── 2.json
│ │ ├── 20.json
│ │ ├── 21.json
│ │ ├── 22.json
│ │ ├── 23.json
│ │ ├── 24.json
│ │ ├── 3.json
│ │ ├── 4.json
│ │ ├── 5.json
│ │ ├── 6.json
│ │ ├── 7.json
│ │ ├── 8.json
│ │ └── 9.json
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── github
│ │ └── jing332
│ │ └── tts_server_android
│ │ ├── DirectUploadEngineTest.kt
│ │ ├── GoLibTest.kt
│ │ ├── HttpTtsUrlTest.kt
│ │ ├── OkHttpTest.kt
│ │ └── RhinoEngineTest.kt
│ ├── debug
│ └── res
│ │ └── values
│ │ └── strings.xml
│ ├── dev
│ └── res
│ │ └── values
│ │ └── strings.xml
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ ├── defaultData
│ │ │ ├── direct_link_upload.js
│ │ │ ├── list.json
│ │ │ ├── plugin-azure.js
│ │ │ └── speech_rule.js
│ │ ├── help
│ │ │ └── app.md
│ │ └── textmate
│ │ │ ├── abyss.json
│ │ │ ├── darcula.json
│ │ │ ├── javascript
│ │ │ ├── language-configuration.json
│ │ │ └── syntaxes
│ │ │ │ └── JavaScript.tmLanguage.json
│ │ │ ├── quietlight.json
│ │ │ └── solarized_drak.json
│ ├── ic_new_app-playstore.png
│ ├── ic_new_app_launcher-playstore.png
│ ├── java
│ │ └── com
│ │ │ └── github
│ │ │ └── jing332
│ │ │ └── tts_server_android
│ │ │ ├── App.kt
│ │ │ ├── AppLocale.kt
│ │ │ ├── CrashHandler.kt
│ │ │ ├── ShortCuts.kt
│ │ │ ├── bean
│ │ │ ├── EdgeVoiceBean.kt
│ │ │ ├── GithubReleaseApiBean.kt
│ │ │ └── LegadoHttpTts.kt
│ │ │ ├── compose
│ │ │ ├── AboutDialog.kt
│ │ │ ├── AppUpdateDialog.kt
│ │ │ ├── AutoUpdateCheckerDialog.kt
│ │ │ ├── LibrariesActivity.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── ShadowReorderableItem.kt
│ │ │ ├── backup
│ │ │ │ ├── BackupDialog.kt
│ │ │ │ ├── BackupRestoreActivity.kt
│ │ │ │ ├── BackupRestoreViewModel.kt
│ │ │ │ ├── RestoreDialog.kt
│ │ │ │ └── Type.kt
│ │ │ ├── codeeditor
│ │ │ │ ├── CodeEditor.kt
│ │ │ │ ├── CodeEditorHelper.kt
│ │ │ │ ├── CodeEditorScreen.kt
│ │ │ │ ├── CodeEditorViewModel.kt
│ │ │ │ ├── LoggerBottomSheet.kt
│ │ │ │ ├── RemoteSyncSettings.kt
│ │ │ │ └── ThemeSettingsDialog.kt
│ │ │ ├── forwarder
│ │ │ │ ├── BasicConfigScreen.kt
│ │ │ │ ├── BasicForwarderScreen.kt
│ │ │ │ ├── ConfigViewModel.kt
│ │ │ │ ├── ForwarderTopAppBar.kt
│ │ │ │ ├── WebScreen.kt
│ │ │ │ ├── ms
│ │ │ │ │ └── MsTtsForwarderScreen.kt
│ │ │ │ └── systts
│ │ │ │ │ └── SystemTtsForwarderScreen.kt
│ │ │ ├── nav
│ │ │ │ ├── NavRoutes.kt
│ │ │ │ └── NavTopAppBar.kt
│ │ │ ├── settings
│ │ │ │ ├── SettingsScreen.kt
│ │ │ │ ├── SettingsWidgets.kt
│ │ │ │ └── ThemeSelectionDialog.kt
│ │ │ ├── systts
│ │ │ │ ├── AuditionDialog.kt
│ │ │ │ ├── ConfigDeleteDialog.kt
│ │ │ │ ├── ConfigExportBottomSheet.kt
│ │ │ │ ├── ConfigImportBottomSheet.kt
│ │ │ │ ├── GroupItem.kt
│ │ │ │ ├── ListSortSettingsDialog.kt
│ │ │ │ ├── LogScreen.kt
│ │ │ │ ├── SystemTtsScreen.kt
│ │ │ │ ├── SystemTtsViewModel.kt
│ │ │ │ ├── TtsLogScreen.kt
│ │ │ │ ├── TtsLogViewModel.kt
│ │ │ │ ├── directlink
│ │ │ │ │ ├── LinkUploadRuleActivity.kt
│ │ │ │ │ └── LinkUploadSelectionDialog.kt
│ │ │ │ ├── list
│ │ │ │ │ ├── BasicAudioParamsDialog.kt
│ │ │ │ │ ├── BgmSettingsDialog.kt
│ │ │ │ │ ├── GlobalAudioParamsDialog.kt
│ │ │ │ │ ├── Group.kt
│ │ │ │ │ ├── GroupAudioParamsDialog.kt
│ │ │ │ │ ├── IntSlider.kt
│ │ │ │ │ ├── InternalPlayerDialog.kt
│ │ │ │ │ ├── Item.kt
│ │ │ │ │ ├── ListExportBottomSheet.kt
│ │ │ │ │ ├── ListImportBottomSheet.kt
│ │ │ │ │ ├── ListManagerScreen.kt
│ │ │ │ │ ├── ListManagerViewModel.kt
│ │ │ │ │ ├── MenuMoreOptions.kt
│ │ │ │ │ ├── PluginSelectionDialog.kt
│ │ │ │ │ ├── SortDialog.kt
│ │ │ │ │ └── edit
│ │ │ │ │ │ ├── BasicInfoEditScreen.kt
│ │ │ │ │ │ ├── QuickEditBottomSheet.kt
│ │ │ │ │ │ ├── TagDataClearConfirmDialog.kt
│ │ │ │ │ │ ├── TtsEditContainerScreen.kt
│ │ │ │ │ │ └── ui
│ │ │ │ │ │ ├── BgmTtsUI.kt
│ │ │ │ │ │ ├── LocalTtsUI.kt
│ │ │ │ │ │ ├── LocalTtsViewModel.kt
│ │ │ │ │ │ ├── MsTtsUI.kt
│ │ │ │ │ │ ├── MsTtsViewModel.kt
│ │ │ │ │ │ ├── PluginTtsUI.kt
│ │ │ │ │ │ ├── PluginTtsViewModel.kt
│ │ │ │ │ │ ├── SaveHandler.kt
│ │ │ │ │ │ ├── TtsUI.kt
│ │ │ │ │ │ ├── TtsUiFactory.kt
│ │ │ │ │ │ └── widgets
│ │ │ │ │ │ ├── AuditionTextField.kt
│ │ │ │ │ │ ├── BaseViewModel.kt
│ │ │ │ │ │ ├── InternalPlayerDialog.kt
│ │ │ │ │ │ └── TtsTopAppBar.kt
│ │ │ │ ├── plugin
│ │ │ │ │ ├── NavRoutes.kt
│ │ │ │ │ ├── PluginEditScreen.kt
│ │ │ │ │ ├── PluginEditViewModel.kt
│ │ │ │ │ ├── PluginExportBottomSheet.kt
│ │ │ │ │ ├── PluginImportBottomSheet.kt
│ │ │ │ │ ├── PluginManagerActivity.kt
│ │ │ │ │ ├── PluginManagerScreen.kt
│ │ │ │ │ ├── PluginManagerViewModel.kt
│ │ │ │ │ ├── PluginPreviewActivity.kt
│ │ │ │ │ └── PluginVarsBottomSheet.kt
│ │ │ │ ├── replace
│ │ │ │ │ ├── Group.kt
│ │ │ │ │ ├── GroupEditDialog.kt
│ │ │ │ │ ├── Item.kt
│ │ │ │ │ ├── NavRoutes.kt
│ │ │ │ │ ├── ReplaceManagerActivity.kt
│ │ │ │ │ ├── ReplaceRuleExportBottomSheet.kt
│ │ │ │ │ ├── ReplaceRuleImportBottomSheet.kt
│ │ │ │ │ ├── ReplaceRuleManagerScreen.kt
│ │ │ │ │ ├── ReplaceRuleManagerViewModel.kt
│ │ │ │ │ ├── SearchTextField.kt
│ │ │ │ │ ├── SortDialog.kt
│ │ │ │ │ └── edit
│ │ │ │ │ │ ├── ReplaceRuleEditScreen.kt
│ │ │ │ │ │ ├── RuleEditViewModel.kt
│ │ │ │ │ │ ├── SoftKeyboardToolbar.kt
│ │ │ │ │ │ ├── ToolBarSettingsDialog.kt
│ │ │ │ │ │ └── TtsConfigSelectDialog.kt
│ │ │ │ └── speechrule
│ │ │ │ │ ├── NavRoutes.kt
│ │ │ │ │ ├── SpeechRuleEditScreen.kt
│ │ │ │ │ ├── SpeechRuleEditViewModel.kt
│ │ │ │ │ ├── SpeechRuleExportBottomSheet.kt
│ │ │ │ │ ├── SpeechRuleImportBottomSheet.kt
│ │ │ │ │ ├── SpeechRuleManagerActivity.kt
│ │ │ │ │ └── SpeechRuleManagerScreen.kt
│ │ │ ├── theme
│ │ │ │ ├── AppTheme.kt
│ │ │ │ ├── AppThemeConfiguration.kt
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Color2.kt
│ │ │ │ ├── Theme.kt
│ │ │ │ └── Type.kt
│ │ │ └── widgets
│ │ │ │ ├── AppBottomSheet.kt
│ │ │ │ ├── AppDialog.kt
│ │ │ │ ├── AppLauncherIcon.kt
│ │ │ │ ├── AppSelectionDialog.kt
│ │ │ │ ├── AppSpinner.kt
│ │ │ │ ├── AppTooltip.kt
│ │ │ │ ├── ButtonToggleGroup.kt
│ │ │ │ ├── CheckMenuItem.kt
│ │ │ │ ├── DenseTextField.kt
│ │ │ │ ├── DropdownTextField.kt
│ │ │ │ ├── EmptyTextToolbar.kt
│ │ │ │ ├── ErrorDialog.kt
│ │ │ │ ├── ExpandableText.kt
│ │ │ │ ├── HtmlText.kt
│ │ │ │ ├── LabelSlider.kt
│ │ │ │ ├── LazyListIndexStateSaver.kt
│ │ │ │ ├── LoadingAnimation.kt
│ │ │ │ ├── LoadingContent.kt
│ │ │ │ ├── LoadingDialog.kt
│ │ │ │ ├── LongClickIconButton.kt
│ │ │ │ ├── Markdown.kt
│ │ │ │ ├── SelectableText.kt
│ │ │ │ ├── SwitchFloatingAction.kt
│ │ │ │ ├── TextCheckBox.kt
│ │ │ │ ├── TextFieldDialog.kt
│ │ │ │ ├── Widgets.kt
│ │ │ │ └── htmlcompose
│ │ │ │ ├── CharacterStyle.kt
│ │ │ │ ├── HtmlText.kt
│ │ │ │ └── MetricAffectingSpan.kt
│ │ │ ├── conf
│ │ │ ├── AppConfig.kt
│ │ │ ├── CodeEditorConfig.kt
│ │ │ ├── DirectUploadConfig.kt
│ │ │ ├── MsForwarderConfig.kt
│ │ │ ├── MsTtsForwarderConfig.kt
│ │ │ ├── PluginConfig.kt
│ │ │ ├── ReplaceRuleConfig.kt
│ │ │ ├── SpeechRuleConfig.kt
│ │ │ ├── SysTtsConfig.kt
│ │ │ ├── SystemTtsConfig.kt
│ │ │ └── SysttsForwarderConfig.kt
│ │ │ ├── constant
│ │ │ ├── AppConst.kt
│ │ │ ├── AppPattern.kt
│ │ │ ├── CnLocalMap.kt
│ │ │ ├── CodeEditorTheme.kt
│ │ │ ├── ConfigType.kt
│ │ │ ├── FilePickerMode.kt
│ │ │ ├── KeyConst.kt
│ │ │ ├── LogLevel.kt
│ │ │ ├── MsTtsApiType.kt
│ │ │ ├── PreferKey.kt
│ │ │ ├── ReplaceExecution.kt
│ │ │ ├── SpeechTarget.kt
│ │ │ └── SystemNotificationConst.kt
│ │ │ ├── data
│ │ │ ├── AppDataBase.kt
│ │ │ ├── DataBaseMigration.kt
│ │ │ ├── dao
│ │ │ │ ├── PluginDao.kt
│ │ │ │ ├── ReplaceRuleDao.kt
│ │ │ │ ├── SpeechRuleDao.kt
│ │ │ │ ├── SysTtsDao.kt
│ │ │ │ └── SystemTtsDao.kt
│ │ │ └── entities
│ │ │ │ ├── AbstractListGroup.kt
│ │ │ │ ├── MapConverters.kt
│ │ │ │ ├── SpeechRule.kt
│ │ │ │ ├── TypeConverterUtils.kt
│ │ │ │ ├── plugin
│ │ │ │ └── Plugin.kt
│ │ │ │ ├── replace
│ │ │ │ ├── GroupWithReplaceRule.kt
│ │ │ │ ├── ReplaceRule.kt
│ │ │ │ └── ReplaceRuleGroup.kt
│ │ │ │ └── systts
│ │ │ │ ├── AudioParams.kt
│ │ │ │ ├── CompatSystemTts.kt
│ │ │ │ ├── GroupWithSystemTts.kt
│ │ │ │ ├── SpeechRuleInfo.kt
│ │ │ │ ├── SystemTts.kt
│ │ │ │ └── SystemTtsGroup.kt
│ │ │ ├── help
│ │ │ ├── ByteArrayBinder.kt
│ │ │ ├── ConfigImportHelper.kt
│ │ │ ├── LocalTtsEngineHelper.kt
│ │ │ └── audio
│ │ │ │ ├── AudioDecoder.kt
│ │ │ │ ├── AudioDecoderException.kt
│ │ │ │ ├── AudioPlayer.kt
│ │ │ │ ├── ByteArrayMediaDataSource.kt
│ │ │ │ ├── ExoAudioPlayer.kt
│ │ │ │ ├── ExoPlayerHelper.kt
│ │ │ │ ├── InputStreamMediaDataSource.kt
│ │ │ │ ├── PcmAudioPlayer.kt
│ │ │ │ ├── Sonic.java
│ │ │ │ └── exo
│ │ │ │ ├── DecoderAudioSink.kt
│ │ │ │ ├── ExoAudioDecoder.kt
│ │ │ │ └── InputStreamDataSource.kt
│ │ │ ├── model
│ │ │ ├── AnalyzeUrl.kt
│ │ │ ├── EdgeTtsLib.kt
│ │ │ ├── LocalTtsEngineHelper.kt
│ │ │ ├── MsTtsEditRepository.kt
│ │ │ ├── hanlp
│ │ │ │ └── HanlpManager.kt
│ │ │ ├── rhino
│ │ │ │ ├── ExceptionExt.kt
│ │ │ │ ├── core
│ │ │ │ │ ├── BaseScriptEngine.kt
│ │ │ │ │ ├── BaseScriptEngineContext.kt
│ │ │ │ │ ├── Logger.kt
│ │ │ │ │ ├── ext
│ │ │ │ │ │ ├── JsCrypto.kt
│ │ │ │ │ │ ├── JsExtensions.kt
│ │ │ │ │ │ ├── JsNet.kt
│ │ │ │ │ │ └── JsUserInterface.kt
│ │ │ │ │ └── type
│ │ │ │ │ │ ├── JClass.kt
│ │ │ │ │ │ ├── ui
│ │ │ │ │ │ ├── Item.kt
│ │ │ │ │ │ ├── JSeekBar.kt
│ │ │ │ │ │ ├── JSpinner.kt
│ │ │ │ │ │ └── JTextInput.kt
│ │ │ │ │ │ └── ws
│ │ │ │ │ │ ├── JWebSocket.kt
│ │ │ │ │ │ └── internal
│ │ │ │ │ │ ├── IWebSocketEvent.kt
│ │ │ │ │ │ ├── WebSocketClient.kt
│ │ │ │ │ │ └── WebSocketException.kt
│ │ │ │ ├── direct_link_upload
│ │ │ │ │ ├── DirectUploadEngine.kt
│ │ │ │ │ └── DirectUploadFunction.kt
│ │ │ │ ├── speech_rule
│ │ │ │ │ ├── ScriptEngineContext.kt
│ │ │ │ │ └── SpeechRuleEngine.kt
│ │ │ │ └── tts
│ │ │ │ │ ├── EngineContext.kt
│ │ │ │ │ ├── TtsPluginEngine.kt
│ │ │ │ │ └── TtsPluginUiEngine.kt
│ │ │ ├── speech
│ │ │ │ ├── ITextToSpeechSynthesizer.kt
│ │ │ │ ├── SynthesizerException.kt
│ │ │ │ ├── TtsTextSegment.kt
│ │ │ │ └── tts
│ │ │ │ │ ├── BaseAudioFormat.kt
│ │ │ │ │ ├── BgmTTS.kt
│ │ │ │ │ ├── EdgeTtsWS.kt
│ │ │ │ │ ├── HttpTTS.kt
│ │ │ │ │ ├── ITextToSpeechEngine.kt
│ │ │ │ │ ├── LocalTTS.kt
│ │ │ │ │ ├── LocalTtsParameter.kt
│ │ │ │ │ ├── MsTTS.kt
│ │ │ │ │ ├── MsTtsAudioFormat.kt
│ │ │ │ │ ├── MsTtsFormatManger.kt
│ │ │ │ │ ├── PlayerParams.kt
│ │ │ │ │ ├── PluginTTS.kt
│ │ │ │ │ └── TtsInfo.kt
│ │ │ └── updater
│ │ │ │ ├── AppUpdateChecker.kt
│ │ │ │ ├── Github.kt
│ │ │ │ ├── UpdateResult.kt
│ │ │ │ └── WorkflowRuns.kt
│ │ │ ├── service
│ │ │ ├── forwarder
│ │ │ │ ├── AbsForwarderService.kt
│ │ │ │ ├── ForwarderServiceManager.kt
│ │ │ │ ├── ms
│ │ │ │ │ ├── MsTtsForwarderService.kt
│ │ │ │ │ └── QSTileService.kt
│ │ │ │ └── system
│ │ │ │ │ ├── EngineInfo.kt
│ │ │ │ │ ├── QSTileService.kt
│ │ │ │ │ ├── SysTtsForwarderService.kt
│ │ │ │ │ └── VoiceInfo.kt
│ │ │ └── systts
│ │ │ │ ├── CheckVoiceData.kt
│ │ │ │ ├── SystemTtsService.kt
│ │ │ │ └── help
│ │ │ │ ├── BgmPlayer.kt
│ │ │ │ ├── SpeechRuleHelper.kt
│ │ │ │ ├── TextReplacer.kt
│ │ │ │ ├── TextToSpeechManager.kt
│ │ │ │ └── exception
│ │ │ │ ├── ConfigLoadException.kt
│ │ │ │ ├── PlayException.kt
│ │ │ │ ├── RequestException.kt
│ │ │ │ ├── SpeechRuleException.kt
│ │ │ │ ├── SynthesisException.kt
│ │ │ │ ├── TextReplacerException.kt
│ │ │ │ └── TtsManagerException.kt
│ │ │ ├── ui
│ │ │ ├── AppActivityResultContracts.kt
│ │ │ ├── AppHelpDocumentActivity.kt
│ │ │ ├── AppLog.kt
│ │ │ ├── ExoPlayerActivity.kt
│ │ │ ├── FilePickerActivity.kt
│ │ │ ├── ImportConfigActivity.kt
│ │ │ ├── forwarder
│ │ │ │ ├── MsForwarderSwitchActivity.kt
│ │ │ │ └── SystemForwarderSwitchActivity.kt
│ │ │ ├── systts
│ │ │ │ ├── ImportConfigFactory.kt
│ │ │ │ └── KeyBoardToolPop.kt
│ │ │ └── view
│ │ │ │ ├── AppDialogs.kt
│ │ │ │ ├── Attributes.kt
│ │ │ │ ├── BigTextView.kt
│ │ │ │ ├── ErrorDialogActivity.kt
│ │ │ │ ├── ErrorDialogViewModel.kt
│ │ │ │ └── widget
│ │ │ │ └── AppTextInputLayout.kt
│ │ │ └── utils
│ │ │ ├── ASFUriUtils.kt
│ │ │ ├── ClipBoardUtils.kt
│ │ │ ├── ConfigurationExtensions.kt
│ │ │ ├── DecimalUtils.kt
│ │ │ ├── EncoderUtils.kt
│ │ │ ├── ExtensionUtils.kt
│ │ │ ├── FileUtils.kt
│ │ │ ├── GcManager.kt
│ │ │ ├── HandlerUtils.kt
│ │ │ ├── LiveDataNoStickyExt.kt
│ │ │ ├── MD5Utils.kt
│ │ │ ├── MyTools.kt
│ │ │ ├── ParcelUtils.kt
│ │ │ ├── SizeUtils.kt
│ │ │ ├── SoftKeyboardUtils.kt
│ │ │ ├── StringUtils.kt
│ │ │ ├── ThrottleUtil.kt
│ │ │ ├── ThrowableUtils.kt
│ │ │ ├── ToastUtils.kt
│ │ │ ├── VarDelegate.kt
│ │ │ └── ZipUtils.kt
│ ├── kotlin
│ │ └── androidx
│ │ │ └── compose
│ │ │ └── material3
│ │ │ └── pullrefresh
│ │ │ ├── PullRefresh.kt
│ │ │ ├── PullRefreshIndicator.kt
│ │ │ ├── PullRefreshIndicatorTransform.kt
│ │ │ └── PullRefreshState.kt
│ └── res
│ │ ├── color
│ │ ├── bg_text_btn_color_selector.xml
│ │ └── tab_text_color.xml
│ │ ├── drawable
│ │ ├── baseline_error_24.xml
│ │ ├── baseline_tag_24.xml
│ │ ├── bg_spinner_item_selector.xml
│ │ ├── ic_app_launcher_foreground.xml
│ │ ├── ic_app_notification.xml
│ │ ├── ic_baseline_add_24.xml
│ │ ├── ic_baseline_compare_arrows_24.xml
│ │ ├── ic_baseline_file_open_24.xml
│ │ ├── ic_baseline_input_24.xml
│ │ ├── ic_baseline_insert_drive_file_24.xml
│ │ ├── ic_baseline_output_24.xml
│ │ ├── ic_baseline_remove_24.xml
│ │ ├── ic_baseline_save_24.xml
│ │ ├── ic_baseline_select_all_24.xml
│ │ ├── ic_config.xml
│ │ ├── ic_microsoft.xml
│ │ ├── ic_microsoft_edge.xml
│ │ ├── ic_shortcut_plugin.xml
│ │ ├── ic_shortcut_replace.xml
│ │ ├── ic_shortcut_speech_rule.xml
│ │ ├── ic_switch.xml
│ │ ├── ic_tts.xml
│ │ └── ic_web.xml
│ │ ├── layout
│ │ ├── big_text_view.xml
│ │ ├── material_text_input.xml
│ │ ├── seekbar.xml
│ │ └── slider.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_app_launcher.xml
│ │ └── ic_app_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_app_launcher.webp
│ │ └── ic_app_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_app_launcher.webp
│ │ └── ic_app_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_app_launcher.webp
│ │ └── ic_app_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_app_launcher.webp
│ │ ├── ic_app_launcher_round.webp
│ │ └── ic_legado_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_app_launcher.webp
│ │ ├── ic_app_launcher_round.webp
│ │ ├── ic_app_notification.png
│ │ └── ic_legado_launcher.png
│ │ ├── values-en
│ │ └── strings.xml
│ │ ├── values-fa
│ │ └── strings.xml
│ │ ├── values-ja
│ │ └── strings.xml
│ │ ├── values-night
│ │ ├── colors.xml
│ │ └── themes.xml
│ │ ├── values-zh-rHK
│ │ └── strings.xml
│ │ ├── values-zh-rTW
│ │ └── strings.xml
│ │ ├── values-zh
│ │ └── strings.xml
│ │ ├── values
│ │ ├── arrays.xml
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ic_new_app_background.xml
│ │ ├── ic_new_app_launcher_background.xml
│ │ ├── keys.xml
│ │ ├── strings.xml
│ │ ├── styles.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── file_paths.xml
│ │ └── tts_engine.xml
│ └── test
│ └── java
│ └── com
│ └── github
│ └── jing332
│ └── tts_server_android
│ └── HttpTtsUrlUnitTest.kt
├── build.gradle
├── crowdin.yml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── 1.jpg
├── 2.jpg
├── 3.jpg
└── 4.jpg
├── settings.gradle
└── tts-server-lib
├── .gitignore
├── README.md
├── api_ms_tts.go
├── api_ms_tts_test.go
├── api_scirpt_code_sync_server.go
├── api_sys_tts_server.go
├── api_sys_tts_server_test.go
├── go.mod
├── go.sum
├── ms_tts_server.go
├── net_tools.go
├── sync_server
└── script_code_sync_server.go
├── sys_tts_server
├── logic.go
├── logic_test.go
├── public
│ └── index.html
├── server.go
└── server_test.go
└── wrapper
└── log.go
/.github/workflows/automerge.yml:
--------------------------------------------------------------------------------
1 | name: Auto merge
2 | on:
3 | pull_request:
4 | types:
5 | - labeled
6 | - unlabeled
7 | - synchronize
8 | - opened
9 | - edited
10 | - ready_for_review
11 | - reopened
12 | - unlocked
13 | pull_request_review:
14 | types:
15 | - submitted
16 | check_suite:
17 | types:
18 | - completed
19 | status: {}
20 | jobs:
21 | automerge:
22 | runs-on: ubuntu-latest
23 | steps:
24 | - id: automerge
25 | name: automerge
26 | uses: "pascalgn/automerge-action@v0.16.2"
27 | env:
28 | MERGE_FILTER_AUTHOR: "jing332"
29 | GITHUB_TOKEN: ${{ secrets.TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea
3 | .gradle
4 | .DS_Store
5 | /build
6 | /captures
7 | .externalNativeBuild
8 | .cxx
9 | local.properties
10 |
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | - 有好的配置导入提示
2 | - 添加三种主题色
3 | - 新用户自动添加默认配置
4 | - 添加帮助文档,并自动显示
5 | - 朗读规则支持单条导出
6 | - 支持由调用者通过API指定发音配置
7 | - 修复朗读规则导出无拓展名
8 | - 修复本地TTS无法在编辑界面试听
9 | - 修复插件TTS附加数据不更新(解决Azure插件风格和角色变化问题)
10 | - 修复Android8及以下版本的备份问题
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /release
3 | /libs/tts_server_lib.aar
--------------------------------------------------------------------------------
/app/libs/README.md:
--------------------------------------------------------------------------------
1 | # libs
2 |
3 | 将 tts-server-lib 目录下的 tts_server_lib.aar 移动到当前目录,以供Android调用。
--------------------------------------------------------------------------------
/app/libs/rhino-1.7.13-1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/libs/rhino-1.7.13-1.jar
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/github/jing332/tts_server_android/GoLibTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import org.junit.Test
5 | import org.junit.runner.RunWith
6 |
7 | @RunWith(AndroidJUnit4::class)
8 | class GoLibTest {
9 | @Test
10 | fun goLib() {
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/github/jing332/tts_server_android/HttpTtsUrlTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android
2 |
3 | import android.util.Log
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import com.github.jing332.tts_server_android.constant.AppConst
6 | import com.github.jing332.tts_server_android.model.AnalyzeUrl
7 | import org.junit.Assert.*
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class HttpTtsUrlTest {
18 | @Test
19 | fun test() {
20 | // Context of the app under test.
21 | // val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 |
23 | // "http://tsn.baidu.com/text2audio,{\"method\": \"POST\", \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4114&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=220&vol=5&aue=6&pit=5&res_tag=audio\"}"
24 | val url =
25 | """ http://192.168.0.109:1233/api/ra ,{"method":"POST","body":"{{String(speakText).replace(/&/g, '&').replace(/\"/g, '"').replace(/'/g, ''').replace(//g, '>')}}"} """
26 | Log.e("TAG", url)
27 | val a = AnalyzeUrl(url, speakText = "t\\\\est\\测\\\\试")
28 | Log.e("TAG", "baseUrl: " + a.eval().toString())
29 |
30 | println(AppConst.SCRIPT_ENGINE.eval(""" String("Test\\\\测试") """))
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/github/jing332/tts_server_android/OkHttpTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import com.drake.net.Net
5 | import okhttp3.MediaType.Companion.toMediaType
6 | import okhttp3.MultipartBody
7 | import okhttp3.RequestBody.Companion.toRequestBody
8 | import okhttp3.Response
9 | import org.junit.Test
10 | import org.junit.runner.RunWith
11 |
12 | @RunWith(AndroidJUnit4::class)
13 | class OkHttpTest {
14 | @Test
15 | fun postMultiPart() {
16 | val json = """
17 | {"11":"11", "22": "22"}
18 | """.trimIndent()
19 | val url = "http://v2.jt12.de/up-v2.php"
20 | val resp: Response = Net.post(url) {
21 | body = MultipartBody.Builder()
22 | .setType(MultipartBody.FORM)
23 | .addFormDataPart(
24 | "file",
25 | "filename.json",
26 | json.toRequestBody("text/javascript".toMediaType())
27 | )
28 | .build()
29 | }.execute()
30 | println("${resp.code} ${resp.message}: ${resp.body?.string()}")
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/github/jing332/tts_server_android/RhinoEngineTest.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import cn.hutool.crypto.symmetric.SymmetricCrypto
5 | import com.script.javascript.RhinoScriptEngine
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 | import org.mozilla.javascript.NativeObject
9 |
10 | @RunWith(AndroidJUnit4::class)
11 | class RhinoEngineTest {
12 | @Test
13 | fun script() {
14 | val jsCode = """
15 |
16 | """.trimIndent()
17 |
18 | RhinoScriptEngine().apply {
19 | val compiledScript = compile(jsCode)
20 | compiledScript.eval()
21 | println((get("tts") as NativeObject).get("name"))
22 | }
23 |
24 | // PluginEngine().apply {
25 | // println(runScript(jsCode, "测试文本", 1))
26 | // }
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | DB·TTS Server
4 |
--------------------------------------------------------------------------------
/app/src/dev/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | D·TTS Server
4 |
--------------------------------------------------------------------------------
/app/src/main/assets/defaultData/direct_link_upload.js:
--------------------------------------------------------------------------------
1 | let DirectUploadJS = {
2 |
3 | "喵公子① (有效期7天)": function(config) {
4 | let url = 'https://sy.mgz6.cc/shuyuan'
5 | let zl = 'https://shuyuan.mgz6.cc/shuyuan'
6 | let resp = upload(url, config)
7 | let result = JSON.parse(resp.body().string())
8 | if (result['msg'] !== 'success') {
9 | throw "error: " + result['msg']
10 | }
11 |
12 | return zl + '/' + result['data']
13 | }
14 | ,
15 | "喵公子②·备用 (有效期7天)": function(config) {
16 | let url = 'https://sy.miaogongzi.cc/shuyuan'
17 | let zl = 'https://shuyuan.miaogongzi.cc/shuyuan'
18 | let resp = upload(url, config)
19 | let result = JSON.parse(resp.body().string())
20 | if (result['msg'] !== 'success') {
21 | throw "error: " + result['msg']
22 | }
23 |
24 | return zl + '/' + result['data']
25 | }
26 | }
27 |
28 | function upload(url, config, extra) {
29 | let form = {
30 | "file":{
31 | 'body': config,
32 | 'fileName': 'config.json',
33 | 'contentType': 'application/json',
34 | }
35 | }
36 |
37 | return ttsrv.httpPostMultipart(url, form)
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/assets/defaultData/list.json:
--------------------------------------------------------------------------------
1 | [
2 |
3 | ]
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/assets/defaultData/speech_rule.js:
--------------------------------------------------------------------------------
1 | let SpeechRuleJS = {
2 | name: "旁白/对话",
3 | id: "ttsrv.multi_voice",
4 | author: "TTS Server",
5 | version: 4,
6 | tags: {narration: "旁白", dialogue: "对话"},
7 |
8 | handleText(text) {
9 | const list = [];
10 | let tmpStr = "";
11 | let endTag = "narration";
12 |
13 | text.split("").forEach((char, index) => {
14 | tmpStr += char;
15 |
16 | if (char === '“') {
17 | endTag = "dialogue";
18 | list.push({text: tmpStr, tag: "narration"});
19 | tmpStr = "";
20 | } else if (char === '”') {
21 | endTag = "narration";
22 | tmpStr = tmpStr.slice(0, -1)
23 | list.push({text: tmpStr, tag: "dialogue"});
24 | tmpStr = "";
25 | } else if (index === text.length - 1) {
26 | list.push({text: tmpStr, tag: endTag});
27 | }
28 | });
29 |
30 | return list;
31 | },
32 |
33 | splitText(text) {
34 | let separatorStr = "。??!!;;"
35 |
36 | let list = []
37 | let tmpStr = ""
38 | text.split("").forEach((char, index) => {
39 | tmpStr += char
40 |
41 | if (separatorStr.includes(char)) {
42 | list.push(tmpStr)
43 | tmpStr = ""
44 | } else if (index === text.length - 1) {
45 | list.push(tmpStr);
46 | }
47 | })
48 |
49 | return list.filter(item => item.replace(/[“”]/g, '').trim().length > 0);
50 | }
51 |
52 | };
53 |
--------------------------------------------------------------------------------
/app/src/main/assets/help/app.md:
--------------------------------------------------------------------------------
1 | version-1
2 |
3 | ### 此帮助文档可在左侧滑菜单打开。
4 |
5 | [](https://h5.qun.qq.com/s/hxc6YglYE8)
6 | [](https://github.com/mgz0227/tts-server-android/issues)
7 | [](https://github.com/mgz0227/tts-server-android/actions/workflows/test.yml)
8 |
9 | # TTS Server
10 | 本应用有3个独立功能,通过左侧滑菜单进行切换。
11 |
12 |
13 | ## 1️⃣ 系统TTS
14 | 以下4个界面,在右上角更多选项中都有独立的导入、导出功能。您还可在设置中进行全部备份、恢复等操作。
15 |
16 | ### 主界面
17 | 配置列表,用于管理TTS配置,您可使用分组功能进行一键切换多个配置。
18 | - 可在设置中调换 `编辑`与`试听` 按钮的位置 ( 长按编辑按钮进行试听,反之,长按试听按钮进行编辑 )
19 |
20 | ### 朗读规则
21 | 用于处理朗读文本,根据用户配置的标签进行匹配TTS配置(如:旁白/对话)。
22 | 程序已内置 基于中文的双引号的 `旁白对话` 朗读规则,您可直接进行使用。
23 |
24 | ### 插件
25 | 用于扩展TTS功能,使用JS脚本进行调用互联网的上的TTS接口,如:内置的`Azure插件`。
26 |
27 | ### 替换规则
28 | 用于替换朗读文本进行纠正发音等操作,如:将“你好”替换为“您好”
29 |
30 | 高级示例:
31 | - 将字数5以内的对话的双引号替换为【】,以达到旁白朗读的目的。
32 | ```
33 | (启用正则表达式)
34 | 替换规则:(“)(.{1,5})(”)
35 | 替换为:【$2】
36 | ```
37 |
38 | ## 👨🏫 系统TTS 常见问题
39 | ### 1. 锁屏后一段时间朗读突然停止?
40 | > 在 `系统设置->应用->电池优化` 中将本APP与阅读APP加入电池优化白名单。
41 | >
42 | > 对于本APP,您可在左侧滑菜单中单击 `电池优化白名单` 进行快捷设置。
43 | >
44 | > PS: 对于国内系统,您可能还需对后台任务上锁,启用后台权限等操作。
45 |
46 | ### 2. 段落间隔时间长?
47 | > 一般是由于网络延迟原因,因为 安卓系统TTS 服务的技术限制,导致无法预缓存音频,故每次只能同步获取。
48 |
49 | ### 3. 启动朗读时提示 `⚠️ 缺少{朗读全部},...` ?
50 | > 添加一个`朗读全部`类型的TTS配置并启用。或尝试开启多语音选项使用 `标签`配置
51 |
52 | ### 4. 启动朗读时提示 `⚠️无标签配置,...!`
53 | > 添加一个 `标签` 类型的TTS配置并启用。或尝试关闭多语音选项使用 `朗读全部` 。
54 |
55 |
56 | ## 2️⃣ 系统TTS转发器
57 | 用于将安卓系统TTS转为HTTP网络接口形式,便于在网页调用。
58 |
59 | ## 3️⃣ 微软TTS转发器
60 | 用于将Edge大声朗读接口简化为HTTP网络接口形式,便于`开源阅读`进行调用。
61 |
62 | 这也是`TTS Server`名称的来源:
63 |
64 | 早期,我将 tts-server-go 移植到安卓,即得名 tts-server-android (Github项目名)
--------------------------------------------------------------------------------
/app/src/main/ic_new_app-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/ic_new_app-playstore.png
--------------------------------------------------------------------------------
/app/src/main/ic_new_app_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgz0227/tts-server-android/045eb75f55a70f5c6fca255e300b600d106cb2c0/app/src/main/ic_new_app_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/App.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Application
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.os.Process
8 | import com.github.jing332.tts_server_android.model.hanlp.HanlpManager
9 | import kotlinx.coroutines.DelicateCoroutinesApi
10 | import kotlinx.coroutines.GlobalScope
11 | import kotlinx.coroutines.launch
12 | import java.util.*
13 | import kotlin.properties.Delegates
14 |
15 |
16 | val app: App
17 | inline get() = App.instance
18 |
19 | @Suppress("DEPRECATION")
20 | class App : Application() {
21 | companion object {
22 | const val TAG = "App"
23 | var instance: App by Delegates.notNull()
24 | val context: Context by lazy { instance }
25 | }
26 |
27 | override fun attachBaseContext(base: Context) {
28 | super.attachBaseContext(base.apply { AppLocale.setLocale(base) })
29 | }
30 |
31 | @SuppressLint("SdCardPath")
32 | @OptIn(DelicateCoroutinesApi::class)
33 | override fun onCreate() {
34 | super.onCreate()
35 | instance = this
36 | CrashHandler(this)
37 |
38 | GlobalScope.launch {
39 | HanlpManager.initDir(
40 | context.getExternalFilesDir("hanlp")?.absolutePath
41 | ?: "/data/data/$packageName/files/hanlp"
42 | )
43 | }
44 | }
45 |
46 | @SuppressLint("UnspecifiedImmutableFlag")
47 | fun restart() {
48 | val intent = packageManager.getLaunchIntentForPackage(packageName)!!
49 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
50 | startActivity(intent);
51 | //杀掉以前进程
52 | Process.killProcess(Process.myPid());
53 | }
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/CrashHandler.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android
2 |
3 | import android.content.Context
4 | import com.github.jing332.tts_server_android.constant.AppConst
5 | import com.github.jing332.tts_server_android.utils.ClipboardUtils
6 | import com.github.jing332.tts_server_android.utils.longToast
7 | import com.github.jing332.tts_server_android.utils.runOnUI
8 | import tts_server_lib.Tts_server_lib
9 | import java.time.LocalDateTime
10 |
11 |
12 | class CrashHandler(var context: Context) : Thread.UncaughtExceptionHandler {
13 | private var mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
14 |
15 | init {
16 | Thread.setDefaultUncaughtExceptionHandler(this)
17 | }
18 |
19 | override fun uncaughtException(t: Thread, e: Throwable) {
20 | handleException(e)
21 | mDefaultHandler?.uncaughtException(t, e)
22 | }
23 |
24 | private fun handleException(e: Throwable) {
25 | context.longToast("TTS Server已崩溃 上传日志中 稍后将会复制到剪贴板")
26 | val log = "\n${LocalDateTime.now()}" +
27 | "\n版本代码:${AppConst.appInfo.versionCode}, 版本名称:${AppConst.appInfo.versionName}\n" +
28 | "崩溃详情:\n${e.stackTraceToString()}"
29 | val copyContent: String = try {
30 | if (BuildConfig.DEBUG)
31 | log
32 | else
33 | Tts_server_lib.uploadLog(log)
34 | } catch (e: Exception) {
35 | e.printStackTrace()
36 | log
37 | }
38 |
39 | runOnUI {
40 | ClipboardUtils.copyText("TTS-Server崩溃日志", copyContent)
41 | context.longToast("已将日志复制到剪贴板")
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/bean/EdgeVoiceBean.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.bean
2 |
3 |
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class EdgeVoiceBean(
9 | @SerialName("Gender")
10 | val gender: String,
11 | @SerialName("Locale")
12 | val locale: String,
13 | @SerialName("Name")
14 | val name: String,
15 | @SerialName("ShortName")
16 | val shortName: String,
17 | @SerialName("FriendlyName")
18 | val friendlyName: String,
19 | // @SerialName("Status")
20 | // val status: String,
21 | // @SerialName("SuggestedCodec")
22 | // val suggestedCodec: String,
23 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/bean/GithubReleaseApiBean.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.bean
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class GithubReleaseApiBean(
8 | @SerialName("assets")
9 | val assets: List,
10 | @SerialName("body")
11 | val body: String,
12 | @SerialName("tag_name")
13 | val tagName: String,
14 | )
15 |
16 | @Serializable
17 | data class Asset(
18 | @SerialName("browser_download_url")
19 | val browserDownloadUrl: String,
20 | @SerialName("name")
21 | val name: String,
22 | @SerialName("size")
23 | val size: Int,
24 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/bean/LegadoHttpTts.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.bean
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 |
7 | @Serializable
8 | data class LegadoHttpTts(
9 | @SerialName("header") val header: String = "",
10 | @SerialName("id") val id: Long = 0,
11 | @SerialName("name") val name: String = "",
12 | @SerialName("url") val url: String = ""
13 |
14 | // @SerialName("concurrentRate")
15 | // val concurrentRate: String,
16 | // @SerialName("contentType")
17 | // val contentType: String,
18 | // @SerialName("enabledCookieJar")
19 | // val enabledCookieJar: Boolean,
20 | // @SerialName("lastUpdateTime")
21 | // val lastUpdateTime: Long,
22 | // @SerialName("loginCheckJs")
23 | // val loginCheckJs: String,
24 | // @SerialName("loginUi")
25 | // val loginUi: String,
26 | // @SerialName("loginUrl")
27 | // val loginUrl: String,
28 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/ShadowReorderableItem.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose
2 |
3 | import android.view.HapticFeedbackConstants
4 | import androidx.compose.animation.core.animateDpAsState
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.lazy.LazyItemScope
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.draw.shadow
11 | import androidx.compose.ui.platform.LocalView
12 | import androidx.compose.ui.unit.dp
13 | import org.burnoutcrew.reorderable.ReorderableItem
14 | import org.burnoutcrew.reorderable.ReorderableState
15 |
16 | @Composable
17 | fun LazyItemScope.ShadowReorderableItem(
18 | reorderableState: ReorderableState<*>,
19 | key: Any,
20 | content: @Composable LazyItemScope.(isDragging: Boolean) -> Unit
21 | ) {
22 | val view = LocalView.current
23 | ReorderableItem(reorderableState, key) { isDragging ->
24 | if (isDragging) {
25 | view.isHapticFeedbackEnabled = true
26 | view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
27 | }
28 |
29 | val elevation =
30 | animateDpAsState(if (isDragging) 24.dp else 0.dp, label = "")
31 | Box(
32 | modifier = Modifier
33 | .shadow(elevation.value)
34 | ) {
35 | content(isDragging)
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/backup/Type.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.backup
2 |
3 | import com.github.jing332.tts_server_android.R
4 |
5 |
6 | sealed class Type(val nameStrId: Int) {
7 | companion object {
8 | val typeList by lazy {
9 | listOf(
10 | Preference,
11 | List,
12 | SpeechRule,
13 | ReplaceRule,
14 | Plugin,
15 | PluginVars
16 | )
17 | }
18 | }
19 |
20 | data object Preference : Type(R.string.preference_settings)
21 | data object List : Type(R.string.config_list)
22 | data object SpeechRule : Type(R.string.speech_rule)
23 | data object ReplaceRule : Type(R.string.replace_rule)
24 |
25 | abstract class IPlugin(val id: Int, val includeVars: Boolean) : Type(id)
26 | object Plugin : IPlugin(R.string.plugin, false)
27 | object PluginVars : IPlugin(R.string.plugin_vars, true)
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/codeeditor/CodeEditor.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.codeeditor
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.platform.LocalContext
6 | import androidx.compose.ui.viewinterop.AndroidView
7 | import com.github.jing332.text_searcher.ui.plugin.CodeEditorHelper
8 | import com.github.jing332.tts_server_android.conf.CodeEditorConfig
9 | import io.github.rosemoe.sora.widget.CodeEditor
10 |
11 | @Composable
12 | fun CodeEditor(modifier: Modifier, onUpdate: (CodeEditor) -> Unit) {
13 | val context = LocalContext.current
14 |
15 | AndroidView(modifier = modifier, factory = {
16 | CodeEditor(it).apply {
17 | val helper = CodeEditorHelper(context, this)
18 | helper.initEditor()
19 | tag = helper
20 | }
21 | }, update = {
22 | // val helper = (it.tag as CodeEditorHelper)
23 | onUpdate(it)
24 | })
25 | }
26 |
27 | fun CodeEditor.helper(): CodeEditorHelper {
28 | return tag as CodeEditorHelper
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/codeeditor/ThemeSettingsDialog.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.codeeditor
2 |
3 | import androidx.compose.material3.Text
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import androidx.compose.ui.res.stringResource
10 | import androidx.compose.ui.tooling.preview.Preview
11 | import com.github.jing332.tts_server_android.R
12 | import com.github.jing332.tts_server_android.compose.widgets.AppSelectionDialog
13 | import com.github.jing332.tts_server_android.conf.CodeEditorConfig
14 | import com.github.jing332.tts_server_android.constant.CodeEditorTheme
15 |
16 | @Composable
17 | internal fun ThemeSettingsDialog(onDismissRequest: () -> Unit) {
18 | AppSelectionDialog(
19 | onDismissRequest = onDismissRequest,
20 | title = { Text(stringResource(id = R.string.theme)) },
21 | value = CodeEditorConfig.theme.value,
22 | values = CodeEditorTheme.values().toList(),
23 | entries = CodeEditorTheme.values().map { it.id.ifBlank { stringResource(id = R.string.theme_default) } },
24 | onClick = { value, _ ->
25 | CodeEditorConfig.theme.value = value as CodeEditorTheme
26 | onDismissRequest()
27 | }
28 | )
29 | }
30 |
31 | @Preview
32 | @Composable
33 | fun PreviewEditorThemeDialog() {
34 | var show by remember { mutableStateOf(true) }
35 | if (show) {
36 | ThemeSettingsDialog {
37 | show = false
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/forwarder/ConfigViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.forwarder
2 |
3 | import androidx.compose.foundation.lazy.LazyListState
4 | import androidx.compose.runtime.mutableStateListOf
5 | import androidx.lifecycle.ViewModel
6 | import com.github.jing332.tts_server_android.ui.AppLog
7 |
8 | class ConfigViewModel : ViewModel() {
9 | val logs = mutableStateListOf()
10 | val logState by lazy { LazyListState() }
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/nav/NavRoutes.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.nav
2 |
3 | import androidx.annotation.StringRes
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.material.icons.Icons
6 | import androidx.compose.material.icons.filled.Settings
7 | import androidx.compose.material3.Icon
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.res.painterResource
11 | import androidx.compose.ui.unit.dp
12 | import com.github.jing332.tts_server_android.R
13 |
14 | sealed class NavRoutes(
15 | val id: String,
16 | @StringRes val strId: Int,
17 | val icon: @Composable () -> Unit = {},
18 | ) {
19 | companion object {
20 | val routes by lazy {
21 | listOf(
22 | SystemTTS,
23 | SystemTtsForwarder,
24 | MsTtsForwarder,
25 | Settings
26 | )
27 | }
28 | }
29 |
30 | data object SystemTTS : NavRoutes("system_tts", R.string.system_tts, icon = {
31 | Icon(modifier = Modifier.size(24.dp), painter = painterResource(id = R.drawable.ic_tts), contentDescription = null)
32 | })
33 |
34 | data object SystemTtsForwarder :
35 | NavRoutes("system_tts_forwarder", R.string.forwarder_systts, icon = {
36 | Icon(modifier = Modifier.size(24.dp), painter = painterResource(id = R.drawable.ic_tts), contentDescription = null)
37 | })
38 |
39 | data object MsTtsForwarder : NavRoutes("ms_tts_forwarder", R.string.forwarder_ms, icon = {
40 | Icon(painter = painterResource(id = R.drawable.ic_microsoft), null)
41 | })
42 |
43 | data object Settings : NavRoutes("settings", R.string.settings, icon = {
44 | Icon(Icons.Default.Settings, null)
45 | })
46 |
47 | // =============
48 | data object TtsEdit : NavRoutes("tts_edit", 0) {
49 | const val DATA = "data"
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/ConfigDeleteDialog.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.DeleteForever
5 | import androidx.compose.material3.AlertDialog
6 | import androidx.compose.material3.Icon
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.Text
9 | import androidx.compose.material3.TextButton
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.res.stringResource
12 | import androidx.compose.ui.text.font.FontWeight
13 | import com.github.jing332.tts_server_android.R
14 |
15 | @Composable
16 | fun ConfigDeleteDialog(onDismissRequest: () -> Unit, name: String, onConfirm: () -> Unit) {
17 | AlertDialog(
18 | onDismissRequest = onDismissRequest,
19 | title = { Text(stringResource(id = R.string.is_confirm_delete)) },
20 | text = {
21 | Text(
22 | name,
23 | style = MaterialTheme.typography.titleMedium,
24 | fontWeight = FontWeight.Bold
25 | )
26 | },
27 | confirmButton = {
28 | TextButton(onClick = {
29 | onConfirm()
30 | }) {
31 | Text(
32 | stringResource(id = R.string.delete),
33 | color = MaterialTheme.colorScheme.error,
34 | fontWeight = FontWeight.Bold
35 | )
36 | }
37 | },
38 | dismissButton = {
39 | TextButton(onClick = onDismissRequest) {
40 | Text(stringResource(id = R.string.cancel))
41 | }
42 | },
43 | icon = {
44 | Icon(
45 | Icons.Default.DeleteForever,
46 | contentDescription = null,
47 | tint = MaterialTheme.colorScheme.error
48 | )
49 | }
50 | )
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/SystemTtsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts
2 |
3 | import androidx.compose.foundation.lazy.LazyListState
4 | import androidx.lifecycle.ViewModel
5 |
6 | class SystemTtsViewModel : ViewModel() {
7 |
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/TtsLogViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts
2 |
3 | import androidx.compose.runtime.mutableStateListOf
4 | import androidx.lifecycle.ViewModel
5 | import com.github.jing332.tts_server_android.ui.AppLog
6 |
7 | class TtsLogViewModel : ViewModel() {
8 | val logs = mutableStateListOf()
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/GlobalAudioParamsDialog.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.list
2 |
3 | import androidx.compose.material3.Text
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.res.stringResource
8 | import com.github.jing332.tts_server_android.R
9 | import com.github.jing332.tts_server_android.conf.SystemTtsConfig
10 |
11 | @Composable
12 | fun GlobalAudioParamsDialog(onDismissRequest: () -> Unit) {
13 | var speed by remember { SystemTtsConfig.audioParamsSpeed }
14 | var volume by remember { SystemTtsConfig.audioParamsVolume }
15 | var pitch by remember { SystemTtsConfig.audioParamsPitch }
16 | BasicAudioParamsDialog(
17 | title = { Text(stringResource(id = R.string.audio_params_settings)) },
18 | onDismissRequest = onDismissRequest,
19 |
20 | speedRange = 0.1f..3f,
21 | speed = speed,
22 | onSpeedChange = { speed = it },
23 |
24 | volumeRange = 0.1f..3f,
25 | volume = volume,
26 | onVolumeChange = { volume = it },
27 |
28 | pitchRange = 0.1f..3f,
29 | pitch = pitch,
30 | onPitchChange = { pitch = it },
31 |
32 | onReset = {
33 | speed = 1f
34 | volume = 1f
35 | pitch = 1f
36 | }
37 | )
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/IntSlider.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.list
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import com.github.jing332.tts_server_android.compose.widgets.LabelSlider
6 |
7 | @Composable
8 | fun IntSlider(
9 | modifier: Modifier = Modifier,
10 | label: String,
11 | value: Float,
12 | onValueChange: (Float) -> Unit,
13 | valueRange: ClosedFloatingPointRange
14 | ) {
15 | LabelSlider(
16 | modifier = modifier,
17 | value = value,
18 | onValueChange = onValueChange,
19 | valueRange = valueRange,
20 | text = label,
21 | buttonSteps = 1f,
22 | buttonLongSteps = 10f
23 | )
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/ListExportBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.list
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.github.jing332.tts_server_android.compose.systts.ConfigExportBottomSheet
5 | import com.github.jing332.tts_server_android.constant.AppConst
6 | import com.github.jing332.tts_server_android.data.entities.systts.GroupWithSystemTts
7 | import kotlinx.serialization.encodeToString
8 |
9 | @Composable
10 | fun ListExportBottomSheet(onDismissRequest: () -> Unit, list: List) {
11 | ConfigExportBottomSheet(
12 | json = AppConst.jsonBuilder.encodeToString(list),
13 | onDismissRequest = onDismissRequest,
14 | fileName = "ttsrv-list.json"
15 | )
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/TagDataClearConfirmDialog.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.list.edit
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.material3.Text
5 | import androidx.compose.material3.TextButton
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.res.stringResource
8 | import androidx.compose.ui.text.font.FontWeight
9 | import com.github.jing332.tts_server_android.R
10 | import com.github.jing332.tts_server_android.compose.widgets.AppDialog
11 |
12 | @Composable
13 | fun TagDataClearConfirmDialog(
14 | tagData: String,
15 | onDismissRequest: () -> Unit,
16 | onConfirm: () -> Unit
17 | ) {
18 | AppDialog(
19 | title = { Text(stringResource(id = R.string.tag_data_clear_warn)) },
20 | content = { Text(tagData, style = MaterialTheme.typography.bodySmall) },
21 | buttons = {
22 | TextButton(onClick = onDismissRequest) {
23 | Text(stringResource(id = R.string.cancel))
24 | }
25 | TextButton(onClick = onConfirm) {
26 | Text(
27 | stringResource(id = R.string.delete),
28 | fontWeight = FontWeight.Bold,
29 | color = MaterialTheme.colorScheme.error
30 | )
31 | }
32 | },
33 | onDismissRequest = onDismissRequest
34 | )
35 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/LocalTtsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui
2 |
3 | import android.speech.tts.TextToSpeech
4 | import android.speech.tts.Voice
5 | import androidx.compose.runtime.mutableStateListOf
6 | import androidx.lifecycle.ViewModel
7 | import com.github.jing332.tts_server_android.App
8 | import com.github.jing332.tts_server_android.model.LocalTtsEngine
9 | import java.util.Locale
10 |
11 | class LocalTtsViewModel : ViewModel() {
12 | private val engine by lazy { LocalTtsEngine(App.context) }
13 |
14 | val engines = mutableStateListOf()
15 | val locales = mutableStateListOf()
16 | val voices = mutableStateListOf()
17 |
18 | fun init() {
19 | engines.clear()
20 | engines.addAll(LocalTtsEngine.getEngines())
21 | }
22 |
23 | suspend fun setEngine(engine: String) {
24 | val ok = this.engine.setEngine(engine)
25 | if (!ok) return
26 |
27 | engines.clear()
28 | engines.addAll(LocalTtsEngine.getEngines())
29 | }
30 |
31 | fun updateLocales() {
32 | locales.clear()
33 | locales.addAll(engine.locales)
34 | }
35 |
36 | fun updateVoices(locale: String) {
37 | voices.clear()
38 | voices.addAll(engine.voices
39 | .filter { it.locale.toLanguageTag() == locale }
40 | .sortedBy { it.name }
41 | )
42 | }
43 |
44 | override fun onCleared() {
45 | super.onCleared()
46 |
47 | engine.shutdown()
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/MsTtsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateListOf
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.lifecycle.ViewModel
8 | import androidx.lifecycle.viewModelScope
9 | import com.drake.net.utils.withIO
10 | import com.github.jing332.tts_server_android.constant.MsTtsApiType
11 | import com.github.jing332.tts_server_android.model.GeneralVoiceData
12 | import com.github.jing332.tts_server_android.model.MsTtsEditRepository
13 | import kotlinx.coroutines.Dispatchers
14 | import kotlinx.coroutines.launch
15 |
16 | class MsTtsViewModel : ViewModel() {
17 | companion object {
18 | private val repo: MsTtsEditRepository by lazy { MsTtsEditRepository() }
19 | }
20 |
21 | private var mAllList: List = emptyList()
22 |
23 | var isLoading by mutableStateOf(true)
24 |
25 | val locales = mutableStateListOf>()
26 | val voices = mutableStateListOf()
27 |
28 | suspend fun load(){
29 | isLoading = true
30 | mAllList = withIO { repo.voicesByApi(MsTtsApiType.EDGE) }
31 | isLoading = false
32 | }
33 |
34 | fun updateLocales() {
35 | locales.clear()
36 | locales.addAll(
37 | mAllList.map { it.locale to it.localeName }.distinctBy { it.first }
38 | .sortedBy { it.first }
39 | )
40 | }
41 |
42 | fun onLocaleChanged(locale: String) {
43 | voices.clear()
44 | // if (locale.isEmpty()) return
45 | voices.addAll(mAllList.filter { it.locale == locale }.sortedBy { it.voiceName })
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/SaveHandler.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.DisposableEffect
5 | import androidx.compose.runtime.remember
6 | import androidx.compose.runtime.staticCompositionLocalOf
7 |
8 | internal val LocalSaveCallBack =
9 | staticCompositionLocalOf> { mutableListOf() }
10 |
11 | internal fun interface SaveCallBack {
12 | suspend fun onSave(): Boolean
13 | }
14 |
15 | @Composable
16 | internal fun rememberSaveCallBacks() = remember { mutableListOf() }
17 |
18 | @Composable
19 | internal fun SaveActionHandler(cb: SaveCallBack) {
20 | val cbs = LocalSaveCallBack.current
21 | DisposableEffect(Unit) {
22 | cbs.add(cb)
23 | onDispose {
24 | cbs.remove(cb)
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/TtsUI.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.MutableState
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.Modifier
8 | import com.github.jing332.tts_server_android.data.entities.systts.SystemTts
9 | import kotlinx.coroutines.CoroutineScope
10 |
11 | typealias CallbackState = MutableState<(suspend () -> Unit)?>
12 | typealias CallbackStateWithRet = MutableState<(suspend CoroutineScope.() -> Boolean)?>
13 |
14 | @Composable
15 | fun rememberCallbackState() =
16 | remember { mutableStateOf<(suspend () -> Unit)?>(null) }
17 |
18 | @Composable
19 | fun rememberCallbackStateWithRet() =
20 | remember { mutableStateOf<(suspend CoroutineScope.() -> Boolean)?>(null) }
21 |
22 | open class TtsUI {
23 | @Composable
24 | open fun ParamsEditScreen(
25 | modifier: Modifier,
26 | systts: SystemTts,
27 | onSysttsChange: (SystemTts) -> Unit
28 | ) {
29 | }
30 |
31 |
32 | @Composable
33 | open fun FullEditScreen(
34 | modifier: Modifier,
35 | systts: SystemTts,
36 | onSysttsChange: (SystemTts) -> Unit,
37 | onSave: () -> Unit,
38 | onCancel: () -> Unit
39 | ) {
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/TtsUiFactory.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui
2 |
3 | import com.github.jing332.tts_server_android.model.speech.tts.BgmTTS
4 | import com.github.jing332.tts_server_android.model.speech.tts.ITextToSpeechEngine
5 | import com.github.jing332.tts_server_android.model.speech.tts.LocalTTS
6 | import com.github.jing332.tts_server_android.model.speech.tts.MsTTS
7 | import com.github.jing332.tts_server_android.model.speech.tts.PluginTTS
8 |
9 | object TtsUiFactory {
10 | fun from(tts: ITextToSpeechEngine): TtsUI? {
11 | return when (tts) {
12 | is PluginTTS -> PluginTtsUI()
13 | is MsTTS -> MsTtsUI()
14 | is LocalTTS -> LocalTtsUI()
15 | is BgmTTS -> BgmTtsUI()
16 |
17 | else -> null
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/widgets/AuditionTextField.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui.widgets
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.Headset
5 | import androidx.compose.material3.Icon
6 | import androidx.compose.material3.IconButton
7 | import androidx.compose.material3.OutlinedTextField
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.res.stringResource
14 | import com.github.jing332.tts_server_android.R
15 | import com.github.jing332.tts_server_android.conf.AppConfig
16 |
17 | @Composable
18 | fun AuditionTextField(modifier: Modifier, onAudition: (String) -> Unit) {
19 | var text by remember { AppConfig.testSampleText }
20 | OutlinedTextField(
21 | modifier = modifier,
22 | label = {Text(stringResource(id = R.string.audition_text))},
23 | value = text,
24 | onValueChange = { text = it },
25 | trailingIcon = {
26 | IconButton(onClick = { onAudition(text) }) {
27 | Icon(Icons.Default.Headset, stringResource(id = R.string.audition))
28 | }
29 | }
30 | )
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/widgets/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui.widgets
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.github.jing332.tts_server_android.app
5 | import com.github.jing332.tts_server_android.help.audio.AudioPlayer
6 |
7 | open class BaseViewModel : ViewModel() {
8 | val audioPlayer by lazy { AudioPlayer(app) }
9 |
10 | override fun onCleared() {
11 | super.onCleared()
12 |
13 | audioPlayer.release()
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/list/edit/ui/widgets/TtsTopAppBar.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.list.edit.ui.widgets
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.ArrowBack
5 | import androidx.compose.material.icons.filled.MoreVert
6 | import androidx.compose.material.icons.filled.Save
7 | import androidx.compose.material3.ExperimentalMaterial3Api
8 | import androidx.compose.material3.Icon
9 | import androidx.compose.material3.IconButton
10 | import androidx.compose.material3.TopAppBar
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.runtime.mutableStateOf
14 | import androidx.compose.runtime.remember
15 | import androidx.compose.runtime.setValue
16 | import androidx.compose.ui.res.stringResource
17 | import com.github.jing332.tts_server_android.R
18 |
19 | @OptIn(ExperimentalMaterial3Api::class)
20 | @Composable
21 | fun TtsTopAppBar(
22 | title: @Composable () -> Unit,
23 | onBackAction: () -> Unit,
24 | onSaveAction: () -> Unit,
25 |
26 | moreOptions: (@Composable (dismiss: () -> Unit) -> Unit)? = null
27 | ) {
28 | TopAppBar(
29 | title = title,
30 | navigationIcon = {
31 | IconButton(onClick = onBackAction) {
32 | Icon(Icons.Default.ArrowBack, stringResource(id = R.string.nav_back))
33 | }
34 | },
35 | actions = {
36 | IconButton(onClick = onSaveAction) {
37 | Icon(Icons.Default.Save, stringResource(id = R.string.save))
38 | }
39 |
40 | var showOptions by remember { mutableStateOf(false) }
41 | if (moreOptions != null)
42 | IconButton(onClick = {
43 |
44 | }) {
45 | Icon(Icons.Default.MoreVert, stringResource(id = R.string.more_options))
46 | moreOptions { showOptions = false }
47 | }
48 | }
49 | )
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/plugin/NavRoutes.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.plugin
2 |
3 | internal sealed class NavRoutes(val id: String) {
4 | data object PluginManager : NavRoutes("plugin_manager")
5 | data object PluginEdit : NavRoutes("plugin_edit") {
6 | const val KEY_DATA = "data"
7 | }
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/plugin/PluginExportBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.plugin
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material3.Checkbox
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.runtime.setValue
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.draw.clip
17 | import androidx.compose.ui.res.stringResource
18 | import androidx.compose.ui.semantics.Role
19 | import androidx.compose.ui.unit.dp
20 | import com.github.jing332.tts_server_android.R
21 | import com.github.jing332.tts_server_android.compose.systts.ConfigExportBottomSheet
22 | import com.github.jing332.tts_server_android.compose.widgets.TextCheckBox
23 |
24 | @Composable
25 | internal fun PluginExportBottomSheet(
26 | onDismissRequest: () -> Unit,
27 | fileName: String,
28 | onGetJson: (isExportVars: Boolean) -> String,
29 | ) {
30 | var isExportVars by remember { mutableStateOf(false) }
31 | ConfigExportBottomSheet(
32 | fileName = fileName,
33 | json = onGetJson(isExportVars),
34 | onDismissRequest = onDismissRequest,
35 | content = {
36 | TextCheckBox(modifier = Modifier
37 | .align(Alignment.CenterHorizontally)
38 | .padding(vertical = 8.dp),
39 | text = { Text(stringResource(id = R.string.export_vars)) },
40 | checked = isExportVars,
41 | onCheckedChange = { isExportVars = !isExportVars })
42 | }
43 | )
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/plugin/PluginImportBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.plugin
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.runtime.setValue
8 | import com.github.jing332.tts_server_android.compose.systts.ConfigImportBottomSheet
9 | import com.github.jing332.tts_server_android.compose.systts.ConfigModel
10 | import com.github.jing332.tts_server_android.compose.systts.SelectImportConfigDialog
11 | import com.github.jing332.tts_server_android.constant.AppConst
12 | import com.github.jing332.tts_server_android.data.appDb
13 | import com.github.jing332.tts_server_android.data.entities.plugin.Plugin
14 |
15 | @Composable
16 | fun PluginImportBottomSheet(onDismissRequest: () -> Unit) {
17 | var list by remember { mutableStateOf?>(null) }
18 | if (list != null) {
19 | SelectImportConfigDialog(
20 | onDismissRequest = { list = null },
21 | models = list!!.map {
22 | ConfigModel(
23 | isSelected = true,
24 | title = it.name,
25 | subtitle = it.author,
26 | it
27 | )
28 | },
29 | onSelectedList = {
30 | appDb.pluginDao.insert(*it.map { plugin -> plugin as Plugin }.toTypedArray())
31 |
32 | it.size
33 | }
34 | )
35 | }
36 |
37 | ConfigImportBottomSheet(onDismissRequest = onDismissRequest, onImport = {
38 | list = AppConst.jsonBuilder.decodeFromString>(it)
39 | })
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/plugin/PluginManagerViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.plugin
2 |
3 | import androidx.lifecycle.ViewModel
4 |
5 | class PluginManagerViewModel : ViewModel() {
6 |
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/replace/NavRoutes.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.replace
2 |
3 | internal sealed class NavRoutes(val id: String) {
4 | data object Manager : NavRoutes("manager")
5 | data object Edit : NavRoutes("edit") {
6 | const val KEY_DATA = "data"
7 | }
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/replace/ReplaceRuleExportBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.replace
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.remember
5 | import com.github.jing332.tts_server_android.compose.systts.ConfigExportBottomSheet
6 | import com.github.jing332.tts_server_android.constant.AppConst
7 | import com.github.jing332.tts_server_android.data.entities.replace.GroupWithReplaceRule
8 | import kotlinx.serialization.encodeToString
9 |
10 | @Composable
11 | fun ReplaceRuleExportBottomSheet(onDismissRequest: () -> Unit, list: List) {
12 | val json = remember(list) {
13 | AppConst.jsonBuilder.encodeToString(list)
14 | }
15 |
16 | ConfigExportBottomSheet(json = json, onDismissRequest = onDismissRequest, fileName = "ttsrv-replaces.json")
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/replace/edit/RuleEditViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.replace.edit
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.github.jing332.tts_server_android.data.entities.replace.ReplaceRule
5 |
6 | class RuleEditViewModel : ViewModel() {
7 | fun doReplace(rule: ReplaceRule, text: String): String {
8 | return if (rule.isRegex)
9 | Regex(rule.pattern).replace(text, rule.replacement)
10 | else
11 | text.replace(rule.pattern, rule.replacement)
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/speechrule/NavRoutes.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.speechrule
2 |
3 | internal sealed class NavRoutes(val id: String) {
4 | data object SpeechRuleManager : NavRoutes("manager")
5 | data object SpeechRuleEdit : NavRoutes("edit") {
6 | const val KEY_DATA = "data"
7 | }
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/speechrule/SpeechRuleExportBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.speechrule
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.github.jing332.tts_server_android.compose.systts.ConfigExportBottomSheet
5 | import com.github.jing332.tts_server_android.constant.AppConst
6 | import com.github.jing332.tts_server_android.data.entities.SpeechRule
7 | import kotlinx.serialization.encodeToString
8 |
9 | @Composable
10 | fun SpeechRuleExportBottomSheet(onDismissRequest: () -> Unit, list: List) {
11 | ConfigExportBottomSheet(
12 | onDismissRequest = onDismissRequest,
13 | json = AppConst.jsonBuilder.encodeToString(list),
14 | fileName = if (list.size == 1) "ttsrv-speechRule-${list[0].name}.json" else "ttsrv-speechRules.json"
15 | )
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/systts/speechrule/SpeechRuleImportBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.systts.speechrule
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.runtime.setValue
8 | import com.github.jing332.tts_server_android.compose.systts.ConfigImportBottomSheet
9 | import com.github.jing332.tts_server_android.compose.systts.ConfigModel
10 | import com.github.jing332.tts_server_android.compose.systts.SelectImportConfigDialog
11 | import com.github.jing332.tts_server_android.constant.AppConst
12 | import com.github.jing332.tts_server_android.data.appDb
13 | import com.github.jing332.tts_server_android.data.entities.SpeechRule
14 |
15 | @Composable
16 | fun SpeechRuleImportBottomSheet(onDismissRequest: () -> Unit) {
17 | var showSelectDialog by remember { mutableStateOf?>(null) }
18 | if (showSelectDialog != null) {
19 | val list = showSelectDialog!!
20 | SelectImportConfigDialog(
21 | onDismissRequest = { showSelectDialog = null },
22 | models = list.map { ConfigModel(true, it.name, "${it.author} - v${it.version}", it) },
23 | onSelectedList = {
24 | appDb.speechRuleDao.insert(*it.map { speechRule -> speechRule as SpeechRule }.toTypedArray())
25 |
26 | it.size
27 | }
28 | )
29 | }
30 |
31 | ConfigImportBottomSheet(onDismissRequest = onDismissRequest, onImport = {
32 | showSelectDialog = AppConst.jsonBuilder.decodeFromString>(it)
33 | })
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/theme/AppTheme.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import com.github.jing332.tts_server_android.R
5 |
6 | enum class AppTheme(val id: String, val stringResId: Int = -1, val color: Color) {
7 | DEFAULT("", R.string.theme_default, green_seed),
8 | DYNAMIC_COLOR("dynamicColor", R.string.dynamic_color, Color.Unspecified),
9 | GREEN("green", R.string.green, green_seed),
10 | RED("red", R.string.red, red_seed),
11 | PINK("pink", R.string.pink, pink_seed),
12 | BLUE("blue", R.string.blue, blue_seed),
13 | CYAN("cyan", R.string.cyan, cyan_seed),
14 | ORANGE("orange", R.string.orange, orange_seed),
15 | PURPLE("purple", R.string.purple, purple_seed),
16 | BROWN("brown", R.string.brown, brown_seed),
17 | GRAY("gray", R.string.gray, gray_seed),
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple80 = Color(0xFFD0BCFF)
6 | val PurpleGrey80 = Color(0xFFCCC2DC)
7 | val Pink80 = Color(0xFFEFB8C8)
8 |
9 | val Purple40 = Color(0xFF6650a4)
10 | val PurpleGrey40 = Color(0xFF625b71)
11 | val Pink40 = Color(0xFF7D5260)
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/AppBottomSheet.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.widgets
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.LaunchedEffect
6 | import androidx.compose.runtime.rememberCoroutineScope
7 | import androidx.compose.ui.Modifier
8 | import com.dokar.sheets.BottomSheetValue
9 | import com.dokar.sheets.m3.BottomSheet
10 | import com.dokar.sheets.rememberBottomSheetState
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.delay
13 | import kotlinx.coroutines.launch
14 |
15 | @Composable
16 | fun AppBottomSheet(
17 | value: BottomSheetValue = BottomSheetValue.Peeked,
18 | onDismissRequest: () -> Unit,
19 | content: @Composable () -> Unit
20 | ) {
21 | val scope = rememberCoroutineScope()
22 | val state = rememberBottomSheetState(confirmValueChange = {
23 | if (it == BottomSheetValue.Collapsed) {
24 | scope.launch(Dispatchers.Main) {
25 | delay(200)
26 | onDismissRequest()
27 | }
28 | }
29 |
30 | true
31 | })
32 | LaunchedEffect(value) {
33 | when (value) {
34 | BottomSheetValue.Collapsed -> state.collapse()
35 | BottomSheetValue.Expanded -> state.expand()
36 | BottomSheetValue.Peeked -> state.peek()
37 | }
38 | }
39 | BottomSheet(
40 | modifier = Modifier.fillMaxSize(),
41 | state = state,
42 | content = content,
43 | )
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/AppTooltip.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.widgets
2 |
3 | import androidx.compose.material3.ExperimentalMaterial3Api
4 | import androidx.compose.material3.PlainTooltip
5 | import androidx.compose.material3.Text
6 | import androidx.compose.material3.TooltipBox
7 | import androidx.compose.material3.TooltipDefaults
8 | import androidx.compose.material3.rememberTooltipState
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 |
12 | @OptIn(ExperimentalMaterial3Api::class)
13 | @Composable
14 | fun AppTooltip(
15 | modifier: Modifier = Modifier,
16 | tooltip: String,
17 | content: @Composable (tooltip: String) -> Unit
18 | ) {
19 | val state = rememberTooltipState()
20 | TooltipBox(
21 | modifier = modifier,
22 | positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
23 | tooltip = {
24 | PlainTooltip {
25 | Text(tooltip)
26 | }
27 | },
28 | state = state,
29 | content = { content(tooltip) },
30 | )
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/CheckMenuItem.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.widgets
2 |
3 | import androidx.compose.foundation.interaction.MutableInteractionSource
4 | import androidx.compose.foundation.layout.PaddingValues
5 | import androidx.compose.material3.Checkbox
6 | import androidx.compose.material3.DropdownMenuItem
7 | import androidx.compose.material3.MenuDefaults
8 | import androidx.compose.material3.MenuItemColors
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.ui.Modifier
12 |
13 | @Composable
14 | fun CheckedMenuItem(
15 | modifier: Modifier = Modifier,
16 | text: @Composable () -> Unit,
17 | checked: Boolean,
18 | onClick: (Boolean) -> Unit,
19 | onClickCheckBox: (Boolean) -> Unit = onClick,
20 | leadingIcon: @Composable (() -> Unit)? = null,
21 | enabled: Boolean = true,
22 | colors: MenuItemColors = MenuDefaults.itemColors(),
23 | contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding,
24 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
25 | ) {
26 | DropdownMenuItem(
27 | onClick = { onClick(!checked) },
28 | modifier = modifier,
29 | enabled = enabled,
30 | contentPadding = contentPadding,
31 | interactionSource = interactionSource,
32 | colors = colors,
33 | text = text,
34 | leadingIcon = leadingIcon,
35 | trailingIcon = {
36 | Checkbox(
37 | checked = checked,
38 | onCheckedChange = { onClickCheckBox.invoke(it) },
39 | enabled = enabled,
40 | )
41 | }
42 | )
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/EmptyTextToolbar.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.widgets
2 |
3 | import androidx.compose.ui.geometry.Rect
4 | import androidx.compose.ui.platform.TextToolbar
5 | import androidx.compose.ui.platform.TextToolbarStatus
6 |
7 | object EmptyTextToolbar : TextToolbar {
8 | override val status: TextToolbarStatus = TextToolbarStatus.Hidden
9 |
10 | override fun hide() {}
11 |
12 | override fun showMenu(
13 | rect: Rect,
14 | onCopyRequested: (() -> Unit)?,
15 | onPasteRequested: (() -> Unit)?,
16 | onCutRequested: (() -> Unit)?,
17 | onSelectAllRequested: (() -> Unit)?,
18 | ) {
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/LazyListIndexStateSaver.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.widgets
2 |
3 | import androidx.compose.foundation.lazy.LazyListState
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.DisposableEffect
6 | import androidx.compose.runtime.LaunchedEffect
7 | import androidx.compose.runtime.getValue
8 | import androidx.compose.runtime.mutableIntStateOf
9 | import androidx.compose.runtime.saveable.rememberSaveable
10 | import androidx.compose.runtime.setValue
11 |
12 | @Composable
13 | fun LazyListIndexStateSaver(
14 | models: Any?,
15 | listState: LazyListState,
16 |
17 | onIndexUpdate: suspend (Int, Int) -> Unit = { index, offset ->
18 | listState.scrollToItem(index, offset)
19 | },
20 | ) {
21 | var index by rememberSaveable { mutableIntStateOf(0) }
22 | var offset by rememberSaveable { mutableIntStateOf(0) }
23 |
24 | LaunchedEffect(models) {
25 | if (models != null && listState.firstVisibleItemIndex <= 0 && listState.firstVisibleItemScrollOffset <= 0) {
26 | onIndexUpdate(index, offset)
27 | }
28 | }
29 |
30 | DisposableEffect(Unit) {
31 | onDispose {
32 | index = listState.firstVisibleItemIndex
33 | offset = listState.firstVisibleItemScrollOffset
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/TextCheckBox.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.widgets
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.RowScope
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material3.Checkbox
9 | import androidx.compose.material3.MaterialTheme
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.draw.clip
14 | import androidx.compose.ui.semantics.Role
15 | import androidx.compose.ui.semantics.semantics
16 | import androidx.compose.ui.unit.dp
17 | import com.github.jing332.tts_server_android.utils.clickableRipple
18 |
19 | @Composable
20 | fun TextCheckBox(
21 | modifier: Modifier = Modifier,
22 | text: @Composable RowScope.() -> Unit,
23 | checked: Boolean,
24 | onCheckedChange: (Boolean) -> Unit,
25 |
26 | horizontalArrangement: Arrangement.Horizontal = Arrangement.Center,
27 | verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
28 | ) {
29 | Row(
30 | modifier
31 | .height(48.dp)
32 | .clip(MaterialTheme.shapes.small)
33 | .clickableRipple(role = Role.Checkbox) { onCheckedChange(!checked) }
34 | ,
35 | verticalAlignment = verticalAlignment,
36 | horizontalArrangement = horizontalArrangement,
37 | ) {
38 | Row(Modifier.padding(horizontal = 8.dp)) {
39 | Checkbox(checked = checked, onCheckedChange = null)
40 | text()
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/TextFieldDialog.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.widgets
2 |
3 | import androidx.compose.material3.AlertDialog
4 | import androidx.compose.material3.OutlinedTextField
5 | import androidx.compose.material3.Text
6 | import androidx.compose.material3.TextButton
7 | import androidx.compose.runtime.Composable
8 |
9 | @Composable
10 | fun TextFieldDialog(
11 | title: String,
12 | text: String,
13 | onTextChange: (String) -> Unit,
14 | onDismissRequest: () -> Unit,
15 | onConfirm: () -> Unit
16 | ) {
17 | AlertDialog(onDismissRequest = onDismissRequest,
18 | title = {
19 | Text(title)
20 | },
21 | text = {
22 | OutlinedTextField(value = text, onValueChange = onTextChange)
23 | },
24 | confirmButton = {
25 | TextButton(onClick = onConfirm) {
26 | Text("确定")
27 | }
28 | }
29 | )
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/htmlcompose/CharacterStyle.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.widgets.htmlcompose
2 |
3 | import android.text.style.ForegroundColorSpan
4 | import android.text.style.StrikethroughSpan
5 | import android.text.style.UnderlineSpan
6 | import androidx.compose.ui.graphics.Color
7 | import androidx.compose.ui.text.SpanStyle
8 | import androidx.compose.ui.text.style.TextDecoration
9 |
10 | internal fun UnderlineSpan.spanStyle(): SpanStyle =
11 | SpanStyle(textDecoration = TextDecoration.Underline)
12 |
13 | internal fun ForegroundColorSpan.spanStyle(): SpanStyle =
14 | SpanStyle(color = Color(foregroundColor))
15 |
16 | internal fun StrikethroughSpan.spanStyle(): SpanStyle =
17 | SpanStyle(textDecoration = TextDecoration.LineThrough)
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/compose/widgets/htmlcompose/MetricAffectingSpan.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.compose.widgets.htmlcompose
2 |
3 | import android.graphics.Typeface
4 | import android.text.style.*
5 | import androidx.compose.ui.text.SpanStyle
6 | import androidx.compose.ui.text.font.FontFamily
7 | import androidx.compose.ui.text.font.FontStyle
8 | import androidx.compose.ui.text.font.FontWeight
9 | import androidx.compose.ui.text.style.BaselineShift
10 | import androidx.compose.ui.unit.TextUnit
11 | import androidx.compose.ui.unit.sp
12 | import java.io.File
13 |
14 | private const val PATH_SYSTEM_FONTS_FILE = "/system/etc/fonts.xml"
15 | private const val PATH_SYSTEM_FONTS_DIR = "/system/fonts/"
16 |
17 | internal fun RelativeSizeSpan.spanStyle(fontSize: TextUnit): SpanStyle =
18 | SpanStyle(fontSize = (fontSize.value * sizeChange).sp)
19 |
20 | internal fun StyleSpan.spanStyle(): SpanStyle? = when (style) {
21 | Typeface.BOLD -> SpanStyle(fontWeight = FontWeight.Bold)
22 | Typeface.ITALIC -> SpanStyle(fontStyle = FontStyle.Italic)
23 | Typeface.BOLD_ITALIC -> SpanStyle(
24 | fontWeight = FontWeight.Bold,
25 | fontStyle = FontStyle.Italic,
26 | )
27 | else -> null
28 | }
29 |
30 | internal fun SubscriptSpan.spanStyle(): SpanStyle =
31 | SpanStyle(baselineShift = BaselineShift.Subscript)
32 |
33 | internal fun SuperscriptSpan.spanStyle(): SpanStyle =
34 | SpanStyle(baselineShift = BaselineShift.Superscript)
35 |
36 | internal fun TypefaceSpan.spanStyle(): SpanStyle? {
37 | val xmlContent = File(PATH_SYSTEM_FONTS_FILE).readText()
38 | return if (xmlContent.contains("""""")
41 | val fontName = familyChunkXml.substringAfter("""""")
42 | .substringBefore("")
43 | SpanStyle(fontFamily = FontFamily(Typeface.createFromFile("$PATH_SYSTEM_FONTS_DIR$fontName")))
44 | } else {
45 | null
46 | }
47 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/conf/CodeEditorConfig.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.conf
2 |
3 | import com.funny.data_saver.core.DataSaverConverter.registerTypeConverters
4 | import com.funny.data_saver.core.DataSaverPreferences
5 | import com.funny.data_saver.core.mutableDataSaverStateOf
6 | import com.github.jing332.tts_server_android.app
7 | import com.github.jing332.tts_server_android.constant.CodeEditorTheme
8 |
9 | object CodeEditorConfig {
10 | private val pref = DataSaverPreferences(app.getSharedPreferences("code_editor", 0))
11 |
12 | init {
13 | registerTypeConverters(
14 | save = { it.id },
15 | restore = { value ->
16 | CodeEditorTheme.values().find { it.id == value } ?: CodeEditorTheme.AUTO
17 | }
18 | )
19 | }
20 |
21 | val theme = mutableDataSaverStateOf(pref, "codeEditorTheme", CodeEditorTheme.AUTO)
22 |
23 | val isWordWrapEnabled = mutableDataSaverStateOf(pref, "isWordWrapEnabled", false)
24 | val isRemoteSyncEnabled = mutableDataSaverStateOf(pref, "isRemoteSyncEnabled", false)
25 | val remoteSyncPort = mutableDataSaverStateOf(pref, "remoteSyncPort", 4566)
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/conf/DirectUploadConfig.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.conf
2 |
3 | import com.funny.data_saver.core.DataSaverPreferences
4 | import com.funny.data_saver.core.mutableDataSaverStateOf
5 | import com.github.jing332.tts_server_android.app
6 | import com.github.jing332.tts_server_android.utils.FileUtils.readAllText
7 |
8 | object DirectUploadConfig {
9 | private val pref = DataSaverPreferences(app.getSharedPreferences("direct_link_upload", 0))
10 |
11 | val code = mutableDataSaverStateOf(
12 | pref,
13 | key = "code",
14 | app.assets.open("defaultData/direct_link_upload.js").readAllText()
15 | )
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/conf/MsForwarderConfig.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.conf
2 |
3 | import com.funny.data_saver.core.DataSaverPreferences
4 | import com.funny.data_saver.core.mutableDataSaverStateOf
5 | import com.github.jing332.tts_server_android.app
6 |
7 | object MsForwarderConfig {
8 | private val pref = DataSaverPreferences(app.getSharedPreferences("server", 0))
9 |
10 | val port = mutableDataSaverStateOf(
11 | dataSaverInterface = pref,
12 | key = "port",
13 | initialValue = 1233
14 | )
15 |
16 | val isWakeLockEnabled = mutableDataSaverStateOf(
17 | dataSaverInterface = pref,
18 | key = "isWakeLockEnabled",
19 | initialValue = false
20 | )
21 |
22 | val token = mutableDataSaverStateOf(
23 | dataSaverInterface = pref,
24 | key = "token",
25 | initialValue = "",
26 | )
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/conf/MsTtsForwarderConfig.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.conf
2 |
3 | import com.funny.data_saver.core.DataSaverPreferences
4 | import com.funny.data_saver.core.mutableDataSaverStateOf
5 | import com.github.jing332.tts_server_android.app
6 |
7 | object MsTtsForwarderConfig {
8 | private val pref = DataSaverPreferences(app.getSharedPreferences("server", 0))
9 |
10 | val port = mutableDataSaverStateOf(pref, key = "port", 1233)
11 | val isWakeLockEnabled = mutableDataSaverStateOf(pref, key = "isWakeLockEnabled", false)
12 | val token = mutableDataSaverStateOf(pref, key = "token", "")
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/conf/PluginConfig.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.conf
2 |
3 | import com.funny.data_saver.core.DataSaverPreferences
4 | import com.funny.data_saver.core.mutableDataSaverStateOf
5 | import com.github.jing332.tts_server_android.app
6 |
7 | object PluginConfig {
8 | private val pref = DataSaverPreferences(app.getSharedPreferences("plugin", 0))
9 |
10 | val textParam = mutableDataSaverStateOf(pref, key = "sampleText", "示例文本。 Sample text.")
11 | // val isSaveRhinoLog = mutableDataSaverStateOf(pref, key = "isSaveRhinoLog", false)
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/jing332/tts_server_android/conf/ReplaceRuleConfig.kt:
--------------------------------------------------------------------------------
1 | package com.github.jing332.tts_server_android.conf
2 |
3 | import com.funny.data_saver.core.DataSaverConverter
4 | import com.funny.data_saver.core.DataSaverMutableState
5 | import com.funny.data_saver.core.DataSaverPreferences
6 | import com.funny.data_saver.core.mutableDataSaverStateOf
7 | import com.github.jing332.tts_server_android.app
8 | import com.github.jing332.tts_server_android.constant.AppConst
9 | import kotlinx.serialization.encodeToString
10 |
11 | object ReplaceRuleConfig {
12 | private val pref = DataSaverPreferences(app.getSharedPreferences("replace_rule", 0))
13 |
14 | val defaultSymbols: LinkedHashMap = listOf(
15 | "(",
16 | ")",
17 | "[",
18 | "]",
19 | "|",
20 | "\\",
21 | "/",
22 | "{",
23 | "}",
24 | "^",
25 | "$",
26 | ".",
27 | "*",
28 | "+",
29 | "?"
30 | ).associateWith { it } as LinkedHashMap
31 |
32 | init {
33 | DataSaverConverter.registerTypeConverters(
34 | save = { AppConst.jsonBuilder.encodeToString(it) },
35 | restore = { value ->
36 | try {
37 | AppConst.jsonBuilder.decodeFromString