diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index fa3034b6a..0c73e47e6 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -69,6 +69,7 @@ object PreferKey { const val webDavAccount = "web_dav_account" const val webDavPassword = "web_dav_password" const val webDavDir = "webDavDir" + const val webServiceAutoStart = "webServiceAutoStart" const val enableCustomExport = "enableCustomExport" const val exportToWebDav = "webDavCacheBackup" const val exportNoChapterName = "exportNoChapterName" @@ -243,4 +244,6 @@ object PreferKey { const val showTip = "showTip" const val sliderVibrator = "sliderVibrator" + const val audioCacheCleanTime = "audioCacheCleanTime" + const val audioPreDownloadNum = "audioPreDownloadNum" } diff --git a/app/src/main/java/io/legado/app/help/config/AppConfig.kt b/app/src/main/java/io/legado/app/help/config/AppConfig.kt index 28fd0a443..a48009e5c 100644 --- a/app/src/main/java/io/legado/app/help/config/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/AppConfig.kt @@ -20,9 +20,6 @@ import io.legado.app.utils.removePref import io.legado.app.utils.sysConfiguration import io.legado.app.utils.toastOnUi import splitties.init.appCtx -// 【新增引用】为了支持文件清理操作 -import java.io.File -import io.legado.app.utils.FileUtils @Suppress("MemberVisibilityCanBePrivate", "ConstPropertyName") object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { @@ -60,7 +57,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { var optimizeRender = CanvasRecorderFactory.isSupport && appCtx.getPrefBoolean(PreferKey.optimizeRender, false) var recordLog = appCtx.getPrefBoolean(PreferKey.recordLog) - + var webServiceAutoStart = appCtx.getPrefBoolean(PreferKey.webServiceAutoStart) override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { when (key) { @@ -976,39 +973,16 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { appCtx.putPrefBoolean(PreferKey.sliderVibrator, value) } - // ================= 自定义功能区域 Start ================= - - // 1. 听书预加载数量 - // 逻辑:从设置里读取字符串,转成数字。如果读不到,默认返回 10。 val audioPreDownloadNum: Int - get() { - // 注意:EditTextPreference 保存的是 String - val str = appCtx.getPrefString("audioPreDownloadNum") - return str?.toIntOrNull() ?: 10 - } + get() = appCtx.getPrefInt(PreferKey.audioPreDownloadNum, 10) + + val audioCacheCleanTimeOrgin: Int + get() = appCtx.getPrefInt(PreferKey.audioCacheCleanTime, 10) - // 2. 音频缓存保留时间 (返回毫秒) - // 逻辑:用户输入的是“分钟”,我们在这里把它乘以 60000 变成“毫秒”。 val audioCacheCleanTime: Long get() { - val str = appCtx.getPrefString("audioCacheCleanTime") - val minutes = str?.toLongOrNull() ?: 10L // 默认 10 分钟 - return minutes * 60 * 1000L + val str = appCtx.getPrefInt(PreferKey.audioCacheCleanTime, 10) + return str * 60 * 1000L } - /** - * 【新增】手动清理 TTS 缓存 - * 这个方法会自动寻找 externalCacheDir(外部存储),如果找不到再找内部。 - * 确保和 Service 里的逻辑保持一致。 - */ - fun clearTtsCache() { - val baseDir = appCtx.externalCacheDir ?: appCtx.cacheDir - // 清理音频文件 - FileUtils.delete(baseDir.absolutePath + File.separator + "httpTTS") - // 清理数据库索引 (可选,建议一起清,防止索引还在但文件没了) - FileUtils.delete(baseDir.absolutePath + File.separator + "httpTTS_cache") - } - - // ================= 自定义功能区域 End ================= - } diff --git a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt index f0ed18fff..42ec0016e 100644 --- a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt @@ -65,11 +65,9 @@ import java.io.File import java.io.InputStream import java.net.ConnectException import java.net.SocketTimeoutException -import kotlin.coroutines.coroutineContext /** * 在线朗读 - * (完美匹配版:修复文件名MD5不一致 + 修复缓存不可见 + 修复0分钟不彻底清理) */ @SuppressLint("UnsafeOptInUsageError") class HttpReadAloudService : BaseReadAloudService(), @@ -78,7 +76,7 @@ class HttpReadAloudService : BaseReadAloudService(), ExoPlayer.Builder(this).build() } - // 【修改点1】改为外部存储,方便你查看文件,确认是否生成 + // 改为外部存储 private val ttsFolderPath: String by lazy { val baseDir = externalCacheDir ?: cacheDir baseDir.absolutePath + File.separator + "httpTTS" + File.separator @@ -454,13 +452,10 @@ class HttpReadAloudService : BaseReadAloudService(), } /** - * 【核心修复点】生成音频文件名 - * 1. 之前的问题:textChapter.title 可能是 "第一章 内容" (带格式),而数据库里是 "第一章 内容" (不带格式)。 - * 2. 现在的做法:强制使用 textChapter.chapter.title (数据库原始标题)。 - * 3. 结果:前台 (播放) 和后台 (下载) 用的标题来源完全一致,MD5 绝对匹配,不会再重复下载了! + * 生成音频文件名 */ private fun md5SpeakFileName(content: String, textChapter: TextChapter? = this.textChapter): String { - val titleToUse = textChapter?.chapter?.title ?: "" // 强制取“核芯”标题 + val titleToUse = textChapter?.chapter?.title ?: "" return MD5Utils.md5Encode16(titleToUse) + "_" + MD5Utils.md5Encode16("${ReadAloud.httpTTS?.url}-|-$speechRate-|-$content") } @@ -492,7 +487,7 @@ class HttpReadAloudService : BaseReadAloudService(), /** * 移除缓存文件 - * 修复逻辑:如果时间设置为0,则不再保护当前章节,退出即全删。 + * 如果时间设置为0,则不再保护当前章节,退出即全删。 */ private fun removeCacheFile() { val keepTime = AppConfig.audioCacheCleanTime diff --git a/app/src/main/java/io/legado/app/service/WebService.kt b/app/src/main/java/io/legado/app/service/WebService.kt index 6e7717742..563812b2d 100644 --- a/app/src/main/java/io/legado/app/service/WebService.kt +++ b/app/src/main/java/io/legado/app/service/WebService.kt @@ -1,5 +1,6 @@ package io.legado.app.service +// ——————【新增引用】—————— import android.annotation.SuppressLint import android.content.Context import android.content.Intent @@ -21,6 +22,7 @@ import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefInt import io.legado.app.utils.postEvent import io.legado.app.utils.printOnDebug +import io.legado.app.utils.putPrefBoolean import io.legado.app.utils.sendToClip import io.legado.app.utils.servicePendingIntent import io.legado.app.utils.startForegroundServiceCompat @@ -33,8 +35,6 @@ import splitties.init.appCtx import splitties.systemservices.powerManager import splitties.systemservices.wifiManager import java.io.IOException -// ——————【新增引用】—————— -import io.legado.app.utils.putPrefBoolean class WebService : BaseService() { @@ -43,9 +43,6 @@ class WebService : BaseService() { var hostAddress = "" fun start(context: Context) { - // ——————【修改开始】记录开启状态—————— - appCtx.putPrefBoolean("web_service_auto", true) - // ——————【修改结束】—————— context.startService() } @@ -55,9 +52,6 @@ class WebService : BaseService() { } fun stop(context: Context) { - // ——————【修改开始】记录关闭状态—————— - appCtx.putPrefBoolean("web_service_auto", false) - // ——————【修改结束】—————— context.stopService() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt index e303cbb5f..8d15ab380 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt @@ -1,5 +1,8 @@ package io.legado.app.ui.book.read.config +//import io.legado.app.lib.theme.backgroundColor +//import io.legado.app.lib.theme.primaryColor +// 【新增引用】为了显示清理成功的提示 import android.content.SharedPreferences import android.os.Bundle import android.view.LayoutInflater @@ -18,16 +21,16 @@ import io.legado.app.help.config.AppConfig import io.legado.app.lib.dialogs.SelectItem import io.legado.app.lib.prefs.SwitchPreference import io.legado.app.lib.prefs.fragment.PreferenceFragment -//import io.legado.app.lib.theme.backgroundColor -//import io.legado.app.lib.theme.primaryColor import io.legado.app.model.ReadAloud import io.legado.app.service.BaseReadAloudService +import io.legado.app.ui.widget.number.NumberPickerDialog import io.legado.app.utils.GSON import io.legado.app.utils.StringUtils +import io.legado.app.utils.TTSCacheUtils import io.legado.app.utils.fromJsonObject import io.legado.app.utils.postEvent +import io.legado.app.utils.putPrefInt import io.legado.app.utils.showDialogFragment -// 【新增引用】为了显示清理成功的提示 import io.legado.app.utils.toastOnUi class ReadAloudConfigDialog : BasePrefDialogFragment() { @@ -73,17 +76,16 @@ class ReadAloudConfigDialog : BasePrefDialogFragment() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_config_aloud) upSpeakEngineSummary() + upPreferenceSummary(PreferKey.audioPreDownloadNum) + upPreferenceSummary(PreferKey.audioCacheCleanTime) findPreference(PreferKey.pauseReadAloudWhilePhoneCalls)?.let { it.isEnabled = AppConfig.ignoreAudioFocus } - // 【新增代码】绑定清理缓存按钮事件 findPreference("clear_cache")?.let { it.summary = getString(R.string.clear_cache) it.setOnPreferenceClickListener { - // 调用 AppConfig 中的万能清理函数 (自动处理外部存储) - AppConfig.clearTtsCache() - // 弹出提示 + TTSCacheUtils.clearTtsCache() toastOnUi("音频缓存已清理") true } @@ -107,6 +109,38 @@ class ReadAloudConfigDialog : BasePrefDialogFragment() { override fun onPreferenceTreeClick(preference: Preference): Boolean { when (preference.key) { + PreferKey.audioPreDownloadNum -> { + NumberPickerDialog(requireContext()) + .setTitle(getString(R.string.read_aloud_preload)) + .setMaxValue(50) + .setMinValue(0) + .setValue(10) + .setCustomButton((R.string.btn_default_s)) { + putPrefInt(PreferKey.audioPreDownloadNum, 10) + upPreferenceSummary(PreferKey.audioPreDownloadNum) + } + .show { + putPrefInt(PreferKey.audioPreDownloadNum, it) + upPreferenceSummary(PreferKey.audioPreDownloadNum) + } + } + + PreferKey.audioCacheCleanTime -> { + NumberPickerDialog(requireContext()) + .setTitle(getString(R.string.audio_cache_clean_time)) + .setMaxValue(50) + .setMinValue(0) + .setValue(1) + .setCustomButton((R.string.btn_default_s)) { + putPrefInt(PreferKey.audioCacheCleanTime, 10) + upPreferenceSummary(PreferKey.audioCacheCleanTime) + } + .show { + putPrefInt(PreferKey.audioCacheCleanTime, it) + upPreferenceSummary(PreferKey.audioCacheCleanTime) + } + } + PreferKey.ttsEngine -> showDialogFragment(SpeakEngineDialog()) "sysTtsConfig" -> IntentHelp.openTTSSetting() } @@ -139,12 +173,34 @@ class ReadAloudConfigDialog : BasePrefDialogFragment() { preference.summary = if (index >= 0) preference.entries[index] else null } + else -> { preference?.summary = value } } } + private fun upPreferenceSummary(preferenceKey: String, value: String? = null) { + val preference = findPreference(preferenceKey) ?: return + when (preferenceKey) { + PreferKey.audioPreDownloadNum -> { + preference.summary = getString( + R.string.read_aloud_preload_summary, + AppConfig.audioPreDownloadNum + ) + } + + PreferKey.audioCacheCleanTime -> { + preference.summary = getString( + R.string.audio_cache_clean_time_summary, + AppConfig.audioCacheCleanTimeOrgin + ) + } + + else -> preference.summary = value + } + } + override fun upSpeakEngineSummary() { upPreferenceSummary( findPreference(PreferKey.ttsEngine), diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt index c0e18e9dc..871fc04b8 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt @@ -1,5 +1,6 @@ package io.legado.app.ui.book.read.config +//import io.legado.app.lib.theme.primaryColor import android.content.Context import android.os.Bundle import android.view.MenuItem @@ -24,15 +25,14 @@ import io.legado.app.help.DirectLinkUpload import io.legado.app.help.config.AppConfig import io.legado.app.lib.dialogs.SelectItem import io.legado.app.lib.dialogs.alert -//import io.legado.app.lib.theme.primaryColor import io.legado.app.model.ReadAloud import io.legado.app.model.ReadBook import io.legado.app.ui.association.ImportHttpTtsDialog import io.legado.app.ui.file.HandleFileContract import io.legado.app.ui.login.SourceLoginActivity import io.legado.app.utils.ACache -import io.legado.app.utils.FileUtils import io.legado.app.utils.GSON +import io.legado.app.utils.TTSCacheUtils import io.legado.app.utils.fromJsonObject import io.legado.app.utils.gone import io.legado.app.utils.isAbsUrl @@ -49,7 +49,6 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.launch -import java.io.File /** * tts引擎管理 @@ -236,19 +235,10 @@ class SpeakEngineDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_recycl adapter.notifyItemRangeChanged(adapter.getHeaderCount(), adapter.itemCount) } - /** - * 【核心修改点】清理缓存方法 - * 原来:只删除 cacheDir (内部存储) - * 现在:调用 AppConfig.clearTtsCache(),自动处理 externalCacheDir (外部存储) - */ fun clearCache() { execute { ReadAloud.upReadAloudClass() - - // 调用万能清理函数 - AppConfig.clearTtsCache() - - // 显示成功提示 + TTSCacheUtils.clearTtsCache() toastOnUi(R.string.clear_cache_success) } } diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index 48158e077..059c2815a 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -42,6 +42,7 @@ import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.storage.Backup import io.legado.app.help.update.AppUpdateGitHub import io.legado.app.lib.dialogs.alert +import io.legado.app.service.WebService import io.legado.app.ui.about.CrashLogsDialog import io.legado.app.ui.about.UpdateDialog import io.legado.app.ui.book.read.ReadBookActivity @@ -71,7 +72,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine -import io.legado.app.service.WebService /** * 主界面 @@ -135,13 +135,11 @@ open class MainActivity : VMBaseActivity(), binding.viewPagerMain.fitsSystemWindows = true // 其他初始化逻辑 setupBackCallback() - - // ——————【修改开始】—————— + // 智能自启:如果上次是手动开启状态(web_service_auto 为 true),则自启 - if (getPrefBoolean("web_service_auto", false)) { + if (AppConfig.webServiceAutoStart) { WebService.startForeground(this) } - // ——————【修改结束】—————— upBottomMenu() initView() diff --git a/app/src/main/java/io/legado/app/utils/TTSCacheUtils.kt b/app/src/main/java/io/legado/app/utils/TTSCacheUtils.kt new file mode 100644 index 000000000..285be0201 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/TTSCacheUtils.kt @@ -0,0 +1,16 @@ +package io.legado.app.utils + +import splitties.init.appCtx +import java.io.File + +object TTSCacheUtils { + + fun clearTtsCache() { + val baseDir = appCtx.externalCacheDir ?: appCtx.cacheDir + // 清理音频文件 + FileUtils.delete(baseDir.absolutePath + File.separator + "httpTTS") + // 清理数据库索引 (可选,建议一起清,防止索引还在但文件没了) + FileUtils.delete(baseDir.absolutePath + File.separator + "httpTTS_cache") + } + +} \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ef43ff54f..450a97c47 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1370,4 +1370,9 @@ 调色板样式 这里的选项仅可以控制使用Compose重写的界面。 启用控件模糊 + 启动应用时打开Web服务 + 听书预加载数量 + %d + 音频缓存保留时间(分钟) + 当前%d, 输入 0 代表退出即刻清理。 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ab05d1ba8..b7501e2a5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1375,4 +1375,9 @@ 调色板样式 这里的选项仅可以控制使用Compose重写的界面。 启用控件模糊 + 启动应用时打开Web服务 + 听书预加载数量 + %d + 音频缓存保留时间(分钟) + 当前%d, 输入 0 代表退出即刻清理。 diff --git a/app/src/main/res/xml/pref_config_aloud.xml b/app/src/main/res/xml/pref_config_aloud.xml index b6b2d366f..a2b1d076a 100644 --- a/app/src/main/res/xml/pref_config_aloud.xml +++ b/app/src/main/res/xml/pref_config_aloud.xml @@ -67,22 +67,16 @@ android:key="sysTtsConfig" app:iconSpaceReserved="false" /> - - diff --git a/app/src/main/res/xml/pref_config_other.xml b/app/src/main/res/xml/pref_config_other.xml index 5c0a85063..34d32d4af 100644 --- a/app/src/main/res/xml/pref_config_other.xml +++ b/app/src/main/res/xml/pref_config_other.xml @@ -19,6 +19,12 @@ app:entries="@array/default_app_variant" app:entryValues="@array/default_app_variant_value" /> + +