diff --git a/README.md b/README.md index d47aaeca5..57ef54751 100644 --- a/README.md +++ b/README.md @@ -38,25 +38,11 @@ Design 3 风格重构版本。 ## 🛠️ 核心功能 -1. **自定义书源:** 强大的爬虫引擎,规则简单易懂,支持自定义抓取网页数据。 -2. **多格式支持:** 支持本地 TXT、EPUB 阅读,智能扫描本地文件。 -3. **高度自定义:** 切换字体、背景、行距、段距、加粗、简繁转换等。 -4. **订阅内容:** 不止是书,可以订阅任何你想看的内容。 -5. **净化替换:** 强力去除广告,替换正文内容。 -6. **翻页模式:** 覆盖、仿真、滑动、滚动等多种模式随心切换。 -7. **完全开源:** 无广告,持续迭代优化。 - ---- - -## 🤝 社区与支持 - -由于本项目的开发者比较忙,暂时没有独立社区。 - -以下为原项目的社区: - -* **官方 Wiki:** [书源规则教程](https://mgz0227.github.io/The-tutorial-of-Legado/) -* **交流社区:** [Telegram 频道](https://t.me/legado_channels) | [Discord](https://discord.gg/VtUfRyzRXn) | [语雀 Wiki](https://www.yuque.com/legado/wiki/community) -* **相关文档:** [免责声明](https://gedoor.github.io/Disclaimer) +1. **多格式支持:** 支持本地 TXT、EPUB 阅读,智能扫描本地文件。 +2. **高度自定义:** 切换字体、背景、行距、段距、加粗、简繁转换等。 +3. **净化替换:** 强力去除广告,替换正文内容。 +4. **翻页模式:** 覆盖、仿真、滑动、滚动等多种模式随心切换。 +5. **完全开源:** 无广告,持续迭代优化。 --- @@ -78,6 +64,5 @@ Design 3 风格重构版本。 ## ⚠️ 免责声明 -本项目为 **Legado(阅读)** 的非官方分支,仅用于学习、交流与个人使用。 项目内容不代表原作者立场,与原项目作者不存在任何隶属或授权关系。 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3d4d585c7..00ea91137 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -211,15 +211,6 @@ android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize|stateHidden" /> - - - - \ No newline at end of file + 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 bfc49a24d..c0f9bcf2f 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -52,6 +52,7 @@ object PreferKey { const val bookshelfLayoutModePortrait = "bookshelfLayoutPortrait" const val bookshelfLayoutModeLandscape = "bookshelf_layout_landscape" const val bookshelfLayoutCompact = "bookshelfLayoutCompact" + const val bookshelfShowDivider = "bookshelfShowDivider" const val bookshelfGridLayout = "bookshelfGridLayout" const val bookshelfSort = "bookshelfSort" const val bookExportFileName = "bookExportFileName" diff --git a/app/src/main/java/io/legado/app/help/config/OldThemeConfig.kt b/app/src/main/java/io/legado/app/help/config/OldThemeConfig.kt index 42a54eef2..ceb8e501b 100644 --- a/app/src/main/java/io/legado/app/help/config/OldThemeConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/OldThemeConfig.kt @@ -168,19 +168,10 @@ object OldThemeConfig { fun applyConfig(context: Context, config: Config) { try { val primary = config.primaryColor.toColorInt() - val accent = config.accentColor.toColorInt() - val background = config.backgroundColor.toColorInt() - val bBackground = config.bottomBackground.toColorInt() if (config.isNightTheme) { context.putPrefInt(PreferKey.cNPrimary, primary) - context.putPrefInt(PreferKey.cNAccent, accent) - context.putPrefInt(PreferKey.cNBackground, background) - context.putPrefInt(PreferKey.cNBBackground, bBackground) } else { context.putPrefInt(PreferKey.cPrimary, primary) - context.putPrefInt(PreferKey.cAccent, accent) - context.putPrefInt(PreferKey.cBackground, background) - context.putPrefInt(PreferKey.cBBackground, bBackground) } AppConfig.isNightTheme = config.isNightTheme applyDayNight(context) @@ -332,4 +323,4 @@ object OldThemeConfig { } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt deleted file mode 100644 index c3076e27a..000000000 --- a/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt +++ /dev/null @@ -1,414 +0,0 @@ -package io.legado.app.ui.config - -import android.content.Context -import android.content.SharedPreferences -import android.os.Bundle -import android.text.InputType -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import androidx.core.view.MenuProvider -import androidx.fragment.app.activityViewModels -import androidx.lifecycle.lifecycleScope -import androidx.preference.EditTextPreference -import androidx.preference.ListPreference -import androidx.preference.Preference -import androidx.preference.PreferenceManager -import io.legado.app.R -import io.legado.app.constant.AppLog -import io.legado.app.constant.PreferKey -import io.legado.app.databinding.DialogWebdavAuthBinding -import io.legado.app.exception.NoStackTraceException -import io.legado.app.help.AppWebDav -import io.legado.app.help.AppWebDav.testWebDav -import io.legado.app.help.config.AppConfig -import io.legado.app.help.config.LocalConfig -import io.legado.app.help.coroutine.Coroutine -import io.legado.app.help.storage.Backup -import io.legado.app.help.storage.BackupConfig -import io.legado.app.help.storage.ImportOldData -import io.legado.app.help.storage.Restore -import io.legado.app.lib.dialogs.alert -import io.legado.app.lib.dialogs.selector -import io.legado.app.lib.permission.Permissions -import io.legado.app.lib.permission.PermissionsCompat -import io.legado.app.lib.prefs.fragment.PreferenceFragment -import io.legado.app.ui.about.AppLogDialog -import io.legado.app.ui.file.HandleFileContract -import io.legado.app.ui.widget.dialog.WaitDialog -import io.legado.app.utils.FileDoc -import io.legado.app.utils.checkWrite -import io.legado.app.utils.getPrefString -import io.legado.app.utils.isContentScheme -import io.legado.app.utils.launch -import io.legado.app.utils.showDialogFragment -import io.legado.app.utils.showHelp -import io.legado.app.utils.toEditable -import io.legado.app.utils.toastOnUi -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.Job -import kotlinx.coroutines.ensureActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import splitties.init.appCtx -import kotlin.coroutines.coroutineContext -import androidx.core.content.edit -import kotlinx.coroutines.currentCoroutineContext - -class BackupConfigFragment : PreferenceFragment(), - SharedPreferences.OnSharedPreferenceChangeListener, - MenuProvider { - - private val viewModel by activityViewModels() - private val waitDialog by lazy { WaitDialog(requireContext()) } - private var backupJob: Job? = null - private var restoreJob: Job? = null - - private val selectBackupPath = registerForActivityResult(HandleFileContract()) { - it.uri?.let { uri -> - if (uri.isContentScheme()) { - AppConfig.backupPath = uri.toString() - } else { - AppConfig.backupPath = uri.path - } - } - } - private val backupDir = registerForActivityResult(HandleFileContract()) { result -> - result.uri?.let { uri -> - if (uri.isContentScheme()) { - AppConfig.backupPath = uri.toString() - backup(uri.toString()) - } else { - uri.path?.let { path -> - AppConfig.backupPath = path - backup(path) - } - } - } - } - private val restoreDoc = registerForActivityResult(HandleFileContract()) { - it.uri?.let { uri -> - waitDialog.setText("恢复中…") - waitDialog.show() - val task = Coroutine.async { - Restore.restore(appCtx, uri) - }.onFinally { - waitDialog.dismiss() - } - waitDialog.setOnCancelListener { - task.cancel() - } - } - } - private val restoreOld = registerForActivityResult(HandleFileContract()) { - it.uri?.let { uri -> - ImportOldData.importUri(appCtx, uri) - } - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.pref_config_backup) - findPreference(PreferKey.webDavDir)?.let { - it.setOnBindEditTextListener { editText -> - editText.text = AppConfig.webDavDir?.toEditable() - } - } - findPreference(PreferKey.webDavDeviceName)?.let { - it.setOnBindEditTextListener { editText -> - editText.text = AppConfig.webDavDeviceName?.toEditable() - } - } - upPreferenceSummary(PreferKey.webDavUrl, getPrefString(PreferKey.webDavUrl)) - upPreferenceSummary(PreferKey.webDavDir, AppConfig.webDavDir) - upPreferenceSummary(PreferKey.webDavDeviceName, AppConfig.webDavDeviceName) - upPreferenceSummary(PreferKey.backupPath, getPrefString(PreferKey.backupPath)) - findPreference("web_dav_restore") - ?.onLongClick { - restoreFromLocal() - true - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - activity?.setTitle(R.string.backup_restore) - preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) - //listView.setEdgeEffectColor(primaryColor) - activity?.addMenuProvider(this, viewLifecycleOwner) - if (!LocalConfig.backupHelpVersionIsLast) { - showHelp("webDavHelp") - } - } - - override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { - menuInflater.inflate(R.menu.backup_restore, menu) - } - - override fun onMenuItemSelected(menuItem: MenuItem): Boolean { - when (menuItem.itemId) { - R.id.menu_help -> { - showHelp("webDavHelp") - return true - } - - R.id.menu_log -> showDialogFragment() - } - return false - } - - override fun onDestroy() { - super.onDestroy() - preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - when (key) { - PreferKey.backupPath -> upPreferenceSummary(key, getPrefString(key)) - PreferKey.webDavUrl, - PreferKey.webDavAccount, - PreferKey.webDavPassword, - PreferKey.webDavDir -> listView.post { - upPreferenceSummary(key, appCtx.getPrefString(key)) - viewModel.upWebDavConfig() - } - - PreferKey.webDavDeviceName -> upPreferenceSummary(key, getPrefString(key)) - } - } - - private fun upPreferenceSummary(preferenceKey: String, value: String?) { - val preference = findPreference(preferenceKey) ?: return - when (preferenceKey) { - PreferKey.webDavUrl -> - if (value.isNullOrBlank()) { - preference.summary = getString(R.string.web_dav_url_s) - } else { - preference.summary = value - } - - PreferKey.webDavDir -> preference.summary = when (value) { - null -> "legado" - else -> value - } - - else -> { - if (preference is ListPreference) { - val index = preference.findIndexOfValue(value) - // Set the summary to reflect the new value. - preference.summary = if (index >= 0) preference.entries[index] else null - } else { - preference.summary = value - } - } - } - } - - override fun onPreferenceTreeClick(preference: Preference): Boolean { - when (preference.key) { - PreferKey.backupPath -> selectBackupPath.launch() - PreferKey.restoreIgnore -> backupIgnore() - "web_dav_test" -> { - lifecycleScope.launch { - testWebDav() - } - } - "web_dav_auth" -> { - showWebDavAuthDialog() - } - "web_dav_backup" -> backup() - "web_dav_restore" -> restore() - "import_old" -> restoreOld.launch() - } - return super.onPreferenceTreeClick(preference) - } - - private fun showWebDavAuthDialog() { - val binding = DialogWebdavAuthBinding.inflate(layoutInflater).apply { - editAccount.setText(PreferenceManager.getDefaultSharedPreferences(requireContext()).getString(PreferKey.webDavAccount, "")) - editPassword.setText(PreferenceManager.getDefaultSharedPreferences(requireContext()).getString(PreferKey.webDavPassword, "")) - editPassword.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD - } - - alert(titleResource = R.string.web_dav_account) { - customView { binding.root } - - okButton { - val account = binding.editAccount.text.toString() - val password = binding.editPassword.text.toString() - - PreferenceManager.getDefaultSharedPreferences(requireContext()).edit { - putString(PreferKey.webDavAccount, account) - .putString(PreferKey.webDavPassword, password) - } - - upPreferenceSummary(PreferKey.webDavAccount, account) - upPreferenceSummary(PreferKey.webDavPassword, password) - lifecycleScope.launch { - testWebDav() - } - } - - cancelButton() - } - } - - /** - * 备份忽略设置 - */ - private fun backupIgnore() { - val checkedItems = BooleanArray(BackupConfig.ignoreKeys.size) { - BackupConfig.ignoreConfig[BackupConfig.ignoreKeys[it]] ?: false - } - alert(R.string.restore_ignore) { - multiChoiceItems(BackupConfig.ignoreTitle, checkedItems) { _, which, isChecked -> - BackupConfig.ignoreConfig[BackupConfig.ignoreKeys[which]] = isChecked - } - onDismiss { - BackupConfig.saveIgnoreConfig() - } - } - } - - - fun backup() { - val backupPath = AppConfig.backupPath - if (backupPath.isNullOrEmpty()) { - backupDir.launch() - } else { - if (backupPath.isContentScheme()) { - lifecycleScope.launch { - val canWrite = withContext(IO) { - FileDoc.fromDir(backupPath).checkWrite() - } - if (canWrite) { - backup(backupPath) - } else { - backupDir.launch() - } - } - } else { - backupUsePermission(backupPath) - } - } - } - - private fun backup(backupPath: String) { - waitDialog.setText("备份中…") - waitDialog.setOnCancelListener { - backupJob?.cancel() - } - waitDialog.show() - backupJob?.cancel() - backupJob = lifecycleScope.launch { - try { - Backup.backupLocked(requireContext(), backupPath) - appCtx.toastOnUi(R.string.backup_success) - } catch (e: Throwable) { - ensureActive() - AppLog.put("备份出错\n${e.localizedMessage}", e) - appCtx.toastOnUi( - appCtx.getString( - R.string.backup_fail, - e.localizedMessage - ) - ) - } finally { - ensureActive() - waitDialog.dismiss() - } - } - } - - private fun backupUsePermission(path: String) { - PermissionsCompat.Builder() - .addPermissions(*Permissions.Group.STORAGE) - .rationale(R.string.tip_perm_request_storage) - .onGranted { - backup(path) - } - .request() - } - - fun restore() { - waitDialog.setText(R.string.loading) - waitDialog.setOnCancelListener { - restoreJob?.cancel() - } - waitDialog.show() - Coroutine.async { - restoreJob = coroutineContext[Job] - showRestoreDialog(requireContext()) - }.onError { - AppLog.put("恢复备份出错WebDavError\n${it.localizedMessage}", it) - if (context == null) { - return@onError - } - alert { - setTitle(R.string.restore) - setMessage("WebDavError\n${it.localizedMessage}\n将从本地备份恢复。") - okButton { - restoreFromLocal() - } - cancelButton() - } - }.onFinally { - waitDialog.dismiss() - } - } - - private suspend fun showRestoreDialog(context: Context) { - val names = withContext(IO) { AppWebDav.getBackupNames() } - if (AppWebDav.isJianGuoYun && names.size > 700) { - context.toastOnUi("由于坚果云限制列出文件数量,部分备份可能未显示,请及时清理旧备份") - } - if (names.isNotEmpty()) { - currentCoroutineContext().ensureActive() - withContext(Main) { - context.selector( - title = context.getString(R.string.select_restore_file), - items = names - ) { _, index -> - if (index in 0 until names.size) { - listView.post { - restoreWebDav(names[index]) - } - } - } - } - } else { - throw NoStackTraceException("Web dav no back up file") - } - } - - private fun restoreWebDav(name: String) { - waitDialog.setText("恢复中…") - waitDialog.show() - val task = Coroutine.async { - AppWebDav.restoreWebDav(name) - }.onError { - AppLog.put("WebDav恢复出错\n${it.localizedMessage}", it) - appCtx.toastOnUi("WebDav恢复出错\n${it.localizedMessage}") - }.onFinally { - waitDialog.dismiss() - } - waitDialog.setOnCancelListener { - task.cancel() - } - } - - private fun restoreFromLocal() { - restoreDoc.launch { - title = getString(R.string.select_restore_file) - mode = HandleFileContract.FILE - allowExtensions = arrayOf("zip") - } - } - - override fun onDestroyView() { - super.onDestroyView() - waitDialog.dismiss() - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/ConfigActivity.kt b/app/src/main/java/io/legado/app/ui/config/ConfigActivity.kt deleted file mode 100644 index 98424c4e6..000000000 --- a/app/src/main/java/io/legado/app/ui/config/ConfigActivity.kt +++ /dev/null @@ -1,46 +0,0 @@ -package io.legado.app.ui.config - -import android.os.Bundle -import androidx.activity.viewModels -import androidx.fragment.app.Fragment -import io.legado.app.R -import io.legado.app.base.VMBaseActivity -import io.legado.app.databinding.ActivityConfigBinding -import io.legado.app.utils.viewbindingdelegate.viewBinding - -class ConfigActivity : VMBaseActivity() { - - override val binding by viewBinding(ActivityConfigBinding::inflate) - override val viewModel by viewModels() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setSupportActionBar(binding.topBar) - when (val configTag = intent.getStringExtra("configTag")) { - ConfigTag.OTHER_CONFIG -> replaceFragment(configTag) - ConfigTag.THEME_CONFIG -> replaceFragment(configTag) - ConfigTag.BACKUP_CONFIG -> replaceFragment(configTag) - ConfigTag.COVER_CONFIG -> replaceFragment(configTag) - ConfigTag.READ_CONFIG -> replaceFragment(configTag) - else -> finish() - } - } - - override fun setTitle(resId: Int) { - super.setTitle(resId) - binding.topBar.setTitle(resId) - binding.topBar.setNavigationOnClickListener { - onBackPressedDispatcher.onBackPressed() - } - } - - inline fun replaceFragment(configTag: String) { - intent.putExtra("configTag", configTag) - @Suppress("DEPRECATION") - val configFragment = supportFragmentManager.findFragmentByTag(configTag) - ?: T::class.java.newInstance() - supportFragmentManager.beginTransaction() - .replace(R.id.configFrameLayout, configFragment, configTag) - .commit() - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt b/app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt new file mode 100644 index 000000000..89db7857f --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt @@ -0,0 +1,78 @@ +package io.legado.app.ui.config + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import io.legado.app.R +import io.legado.app.ui.widget.components.AppScaffold +import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar +import io.legado.app.ui.widget.components.GlassTopAppBarDefaults +import io.legado.app.ui.widget.components.SplicedColumnGroup +import io.legado.app.ui.widget.components.button.TopbarNavigationButton +import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ConfigNavScreen( + onBackClick: () -> Unit, + onNavigateToOther: () -> Unit, + onNavigateToRead: () -> Unit, + onNavigateToCover: () -> Unit, + onNavigateToTheme: () -> Unit, + onNavigateToBackup: () -> Unit +) { + val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() + + AppScaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + GlassMediumFlexibleTopAppBar( + title = { Text(stringResource(R.string.setting)) }, + scrollBehavior = scrollBehavior, + navigationIcon = { + TopbarNavigationButton(onClick = onBackClick) + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .verticalScroll(rememberScrollState()) + .padding(16.dp) + ) { + SplicedColumnGroup { + ClickableSettingItem( + title = stringResource(R.string.theme_setting), + onClick = onNavigateToTheme + ) + ClickableSettingItem( + title = stringResource(R.string.other_setting), + onClick = onNavigateToOther + ) + ClickableSettingItem( + title = stringResource(R.string.read_config), + onClick = onNavigateToRead + ) + ClickableSettingItem( + title = stringResource(R.string.cover_config), + onClick = onNavigateToCover + ) + ClickableSettingItem( + title = stringResource(R.string.backup_restore), + onClick = onNavigateToBackup + ) + } + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/config/ConfigViewModel.kt b/app/src/main/java/io/legado/app/ui/config/ConfigViewModel.kt deleted file mode 100644 index fa6372c2d..000000000 --- a/app/src/main/java/io/legado/app/ui/config/ConfigViewModel.kt +++ /dev/null @@ -1,51 +0,0 @@ -package io.legado.app.ui.config - -import android.app.Application -import android.content.Context -import io.legado.app.R -import io.legado.app.base.BaseViewModel -import io.legado.app.data.appDb -import io.legado.app.help.AppWebDav -import io.legado.app.help.book.BookHelp -import io.legado.app.utils.FileUtils -import io.legado.app.utils.restart -import io.legado.app.utils.toastOnUi -import kotlinx.coroutines.delay -import splitties.init.appCtx - -class ConfigViewModel(application: Application) : BaseViewModel(application) { - - fun upWebDavConfig() { - execute { - AppWebDav.upConfig() - } - } - - fun clearCache() { - execute { - BookHelp.clearCache() - FileUtils.delete(context.cacheDir.absolutePath) - }.onSuccess { - context.toastOnUi(R.string.clear_cache_success) - } - } - - fun clearWebViewData() { - execute { - FileUtils.delete(context.getDir("webview", Context.MODE_PRIVATE)) - FileUtils.delete(context.getDir("hws_webview", Context.MODE_PRIVATE), true) - context.toastOnUi(R.string.clear_webview_data_success) - delay(3000) - appCtx.restart() - } - } - - fun shrinkDatabase() { - execute { - appDb.openHelper.writableDatabase.execSQL("VACUUM") - }.onSuccess { - context.toastOnUi(R.string.success) - } - } - -} diff --git a/app/src/main/java/io/legado/app/ui/config/CoverConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/CoverConfigFragment.kt deleted file mode 100644 index 97f2eec62..000000000 --- a/app/src/main/java/io/legado/app/ui/config/CoverConfigFragment.kt +++ /dev/null @@ -1,168 +0,0 @@ -package io.legado.app.ui.config - -import android.annotation.SuppressLint -import android.content.SharedPreferences -import android.net.Uri -import android.os.Bundle -import android.view.View -import androidx.preference.Preference -import io.legado.app.R -import io.legado.app.constant.PreferKey -import io.legado.app.lib.dialogs.selector -import io.legado.app.lib.prefs.SwitchPreference -import io.legado.app.lib.prefs.fragment.PreferenceFragment -//import io.legado.app.lib.theme.primaryColor -import io.legado.app.model.BookCover -import io.legado.app.utils.* -import splitties.init.appCtx -import java.io.FileOutputStream - -class CoverConfigFragment : PreferenceFragment(), - SharedPreferences.OnSharedPreferenceChangeListener { - - private val requestCodeCover = 111 - private val requestCodeCoverDark = 112 - private val selectImage = registerForActivityResult(SelectImageContract()) { - it.uri?.let { uri -> - when (it.requestCode) { - requestCodeCover -> setCoverFromUri(PreferKey.defaultCover, uri) - requestCodeCoverDark -> setCoverFromUri(PreferKey.defaultCoverDark, uri) - } - } - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.pref_config_cover) - upPreferenceSummary(PreferKey.defaultCover, getPrefString(PreferKey.defaultCover)) - upPreferenceSummary(PreferKey.defaultCoverDark, getPrefString(PreferKey.defaultCoverDark)) - findPreference(PreferKey.coverShowAuthor) - ?.isEnabled = getPrefBoolean(PreferKey.coverShowName) - findPreference(PreferKey.coverShowAuthorN) - ?.isEnabled = getPrefBoolean(PreferKey.coverShowNameN) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - activity?.setTitle(R.string.cover_config) - //listView.setEdgeEffectColor(primaryColor) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) - } - - override fun onDestroy() { - super.onDestroy() - preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - sharedPreferences ?: return - when (key) { - PreferKey.defaultCover, - PreferKey.defaultCoverDark -> { - upPreferenceSummary(key, getPrefString(key)) - } - PreferKey.coverShowName -> { - findPreference(PreferKey.coverShowAuthor) - ?.isEnabled = getPrefBoolean(key) - BookCover.upDefaultCover() - } - PreferKey.coverShowNameN -> { - findPreference(PreferKey.coverShowAuthorN) - ?.isEnabled = getPrefBoolean(key) - BookCover.upDefaultCover() - } - PreferKey.coverTextColor, - PreferKey.coverTextColorN, - PreferKey.coverShadowColor, - PreferKey.coverShadowColorN, - PreferKey.coverDefaultColor, - PreferKey.coverShowShadow, - PreferKey.coverShowStroke, - PreferKey.coverShowAuthor, - PreferKey.coverShowAuthorN -> { - BookCover.upDefaultCover() - } - } - } - - @SuppressLint("PrivateResource") - override fun onPreferenceTreeClick(preference: Preference): Boolean { - when (preference.key) { - "coverRule" -> showDialogFragment(CoverRuleConfigDialog()) - PreferKey.defaultCover -> - if (getPrefString(preference.key).isNullOrEmpty()) { - selectImage.launch(requestCodeCover) - } else { - context?.selector( - items = arrayListOf( - getString(R.string.delete), - getString(R.string.select_image) - ) - ) { _, i -> - if (i == 0) { - removePref(preference.key) - BookCover.upDefaultCover() - } else { - selectImage.launch(requestCodeCover) - } - } - } - PreferKey.defaultCoverDark -> - if (getPrefString(preference.key).isNullOrEmpty()) { - selectImage.launch(requestCodeCoverDark) - } else { - context?.selector( - items = arrayListOf( - getString(R.string.delete), - getString(R.string.select_image) - ) - ) { _, i -> - if (i == 0) { - removePref(preference.key) - BookCover.upDefaultCover() - } else { - selectImage.launch(requestCodeCoverDark) - } - } - } - } - return super.onPreferenceTreeClick(preference) - } - - private fun upPreferenceSummary(preferenceKey: String, value: String?) { - val preference = findPreference(preferenceKey) ?: return - when (preferenceKey) { - PreferKey.defaultCover, - PreferKey.defaultCoverDark -> preference.summary = if (value.isNullOrBlank()) { - getString(R.string.select_image) - } else { - value - } - else -> preference.summary = value - } - } - - private fun setCoverFromUri(preferenceKey: String, uri: Uri) { - readUri(uri) { fileDoc, inputStream -> - kotlin.runCatching { - var file = requireContext().externalFiles - val suffix = fileDoc.name.substringAfterLast(".") - val fileName = uri.inputStream(requireContext()).getOrThrow().use { - MD5Utils.md5Encode(it) + ".$suffix" - } - file = FileUtils.createFileIfNotExist(file, "covers", fileName) - FileOutputStream(file).use { - inputStream.copyTo(it) - } - putPrefString(preferenceKey, file.absolutePath) - BookCover.upDefaultCover() - }.onFailure { - appCtx.toastOnUi(it.localizedMessage) - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt deleted file mode 100644 index 6bfbc71d6..000000000 --- a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt +++ /dev/null @@ -1,401 +0,0 @@ -package io.legado.app.ui.config - -//import io.legado.app.lib.theme.primaryColor -import android.annotation.SuppressLint -import android.content.ComponentName -import android.content.Context -import android.content.SharedPreferences -import android.content.pm.PackageManager -import android.os.Build -import android.os.Bundle -import android.os.PowerManager -import android.view.View -import androidx.core.content.ContextCompat -import androidx.core.view.postDelayed -import androidx.fragment.app.activityViewModels -import androidx.preference.ListPreference -import androidx.preference.Preference -import com.jeremyliao.liveeventbus.LiveEventBus -import io.legado.app.R -import io.legado.app.constant.PreferKey -import io.legado.app.databinding.DialogEditTextBinding -import io.legado.app.help.AppFreezeMonitor -import io.legado.app.help.DispatchersMonitor -import io.legado.app.help.config.AppConfig -import io.legado.app.help.config.LocalConfig -import io.legado.app.lib.dialogs.alert -import io.legado.app.lib.permission.Permissions -import io.legado.app.lib.permission.PermissionsCompat -import io.legado.app.lib.prefs.fragment.PreferenceFragment -import io.legado.app.model.CheckSource -import io.legado.app.model.ImageProvider -import io.legado.app.receiver.SharedReceiverActivity -import io.legado.app.service.WebService -import io.legado.app.ui.file.HandleFileContract -import io.legado.app.ui.widget.number.NumberPickerDialog -import io.legado.app.utils.FirebaseManager -import io.legado.app.utils.LogUtils -import io.legado.app.utils.getPrefBoolean -import io.legado.app.utils.postEvent -import io.legado.app.utils.putPrefBoolean -import io.legado.app.utils.putPrefString -import io.legado.app.utils.removePref -import io.legado.app.utils.restart -import io.legado.app.utils.showDialogFragment -import io.legado.app.utils.startActivity -import splitties.init.appCtx - -/** - * 其它设置 - */ -class OtherConfigFragment : PreferenceFragment(), - SharedPreferences.OnSharedPreferenceChangeListener { - - private val viewModel by activityViewModels() - private val packageManager = appCtx.packageManager - private val componentName = ComponentName( - appCtx, - SharedReceiverActivity::class.java.name - ) - private val localBookTreeSelect = registerForActivityResult(HandleFileContract()) { - it.uri?.let { treeUri -> - AppConfig.defaultBookTreeUri = treeUri.toString() - } - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - putPrefBoolean(PreferKey.processText, isProcessTextEnabled()) - addPreferencesFromResource(R.xml.pref_config_other) - upPreferenceSummary(PreferKey.userAgent, AppConfig.userAgent) - upPreferenceSummary(PreferKey.preDownloadNum, AppConfig.preDownloadNum.toString()) - upPreferenceSummary(PreferKey.threadCount, AppConfig.threadCount.toString()) - upPreferenceSummary(PreferKey.webPort, AppConfig.webPort.toString()) - AppConfig.defaultBookTreeUri?.let { - upPreferenceSummary(PreferKey.defaultBookTreeUri, it) - } - upPreferenceSummary(PreferKey.checkSource, CheckSource.summary) - upPreferenceSummary(PreferKey.bitmapCacheSize, AppConfig.bitmapCacheSize.toString()) - upPreferenceSummary(PreferKey.imageRetainNum, AppConfig.imageRetainNum.toString()) - upPreferenceSummary(PreferKey.sourceEditMaxLine, AppConfig.sourceEditMaxLine.toString()) - updatePermissionSummary() - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - activity?.setTitle(R.string.other_setting) - preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) - //listView.setEdgeEffectColor(primaryColor) - } - - override fun onDestroy() { - super.onDestroy() - preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) - } - - override fun onResume() { - super.onResume() - updatePermissionSummary() - } - - override fun onPreferenceTreeClick(preference: Preference): Boolean { - when (preference.key) { - PreferKey.userAgent -> showUserAgentDialog() - PreferKey.defaultBookTreeUri -> localBookTreeSelect.launch { - title = getString(R.string.select_book_folder) - mode = HandleFileContract.DIR_SYS - } - - PreferKey.preDownloadNum -> NumberPickerDialog(requireContext()) - .setTitle(getString(R.string.pre_download)) - .setMaxValue(9999) - .setMinValue(0) - .setValue(AppConfig.preDownloadNum) - .show { - AppConfig.preDownloadNum = it - } - - PreferKey.threadCount -> NumberPickerDialog(requireContext()) - .setTitle(getString(R.string.threads_num_title)) - .setMaxValue(999) - .setMinValue(1) - .setValue(AppConfig.threadCount) - .show { - AppConfig.threadCount = it - } - - PreferKey.webPort -> NumberPickerDialog(requireContext()) - .setTitle(getString(R.string.web_port_title)) - .setMaxValue(60000) - .setMinValue(1024) - .setValue(AppConfig.webPort) - .show { - AppConfig.webPort = it - } - - PreferKey.cleanCache -> clearCache() - PreferKey.uploadRule -> showDialogFragment() - PreferKey.checkSource -> showDialogFragment() - PreferKey.bitmapCacheSize -> { - NumberPickerDialog(requireContext()) - .setTitle(getString(R.string.bitmap_cache_size)) - .setMaxValue(2047) - .setMinValue(1) - .setValue(AppConfig.bitmapCacheSize) - .show { - AppConfig.bitmapCacheSize = it - ImageProvider.bitmapLruCache.resize(ImageProvider.cacheSize) - } - } - PreferKey.imageRetainNum -> NumberPickerDialog(requireContext()) - .setTitle(getString(R.string.image_retain_number)) - .setMaxValue(999) - .setMinValue(0) - .setValue(AppConfig.imageRetainNum) - .show { - AppConfig.imageRetainNum = it - } - - PreferKey.sourceEditMaxLine -> { - NumberPickerDialog(requireContext()) - .setTitle(getString(R.string.source_edit_text_max_line)) - .setMaxValue(Int.MAX_VALUE) - .setMinValue(10) - .setValue(AppConfig.sourceEditMaxLine) - .show { - AppConfig.sourceEditMaxLine = it - } - } - - PreferKey.clearWebViewData -> clearWebViewData() - "localPassword" -> alertLocalPassword() - PreferKey.shrinkDatabase -> shrinkDatabase() - "TEST" -> startActivity() - PreferKey.notificationsPost -> checkPermission(1) - PreferKey.ignoreBatteryPermission -> checkPermission(2) - } - return super.onPreferenceTreeClick(preference) - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - when (key) { - PreferKey.preDownloadNum -> { - upPreferenceSummary(key, AppConfig.preDownloadNum.toString()) - } - - PreferKey.threadCount -> { - upPreferenceSummary(key, AppConfig.threadCount.toString()) - postEvent(PreferKey.threadCount, "") - } - - PreferKey.webPort -> { - upPreferenceSummary(key, AppConfig.webPort.toString()) - if (WebService.isRun) { - WebService.stop(requireContext()) - WebService.start(requireContext()) - } - } - - PreferKey.defaultBookTreeUri -> { - upPreferenceSummary(key, AppConfig.defaultBookTreeUri) - } - - PreferKey.recordLog -> { - AppConfig.recordLog = appCtx.getPrefBoolean(PreferKey.recordLog) - LogUtils.upLevel() - LogUtils.logDeviceInfo() - LiveEventBus.config().enableLogger(AppConfig.recordLog) - AppFreezeMonitor.init(appCtx) - DispatchersMonitor.init() - } - - PreferKey.processText -> sharedPreferences?.let { - setProcessTextEnable(it.getBoolean(key, true)) - } - - PreferKey.language -> listView.postDelayed(1000) { - appCtx.restart() - } - - PreferKey.userAgent -> listView.post { - upPreferenceSummary(PreferKey.userAgent, AppConfig.userAgent) - } - - PreferKey.checkSource -> listView.post { - upPreferenceSummary(PreferKey.checkSource, CheckSource.summary) - } - - PreferKey.bitmapCacheSize -> { - upPreferenceSummary(key, AppConfig.bitmapCacheSize.toString()) - } - - PreferKey.imageRetainNum -> { - upPreferenceSummary(key, AppConfig.imageRetainNum.toString()) - } - - PreferKey.sourceEditMaxLine -> { - upPreferenceSummary(key, AppConfig.sourceEditMaxLine.toString()) - } - - PreferKey.firebaseEnable -> { - sharedPreferences?.let { - FirebaseManager.setEnabled(requireContext(), it.getBoolean(key, true)) - } - } - - } - } - private fun updatePermissionSummary() { - val notificationsGranted: Boolean = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - ContextCompat.checkSelfPermission( - requireContext(), android.Manifest.permission.POST_NOTIFICATIONS - ) == PackageManager.PERMISSION_GRANTED - } else { - true - } - upPreferenceSummary( - PreferKey.notificationsPost, - if (notificationsGranted) "已获取" else "未获取" - ) - - val pm = requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager - val batteryGranted: Boolean = - pm.isIgnoringBatteryOptimizations(requireContext().packageName) - - upPreferenceSummary( - PreferKey.ignoreBatteryPermission, - if (batteryGranted) "已获取" else "未获取" - ) - } - - - private fun upPreferenceSummary(preferenceKey: String, value: String?) { - val preference = findPreference(preferenceKey) ?: return - when (preferenceKey) { - PreferKey.preDownloadNum -> preference.summary = - getString(R.string.pre_download_s, value) - - PreferKey.threadCount -> preference.summary = getString(R.string.threads_num, value) - PreferKey.webPort -> preference.summary = getString(R.string.web_port_summary, value) - PreferKey.bitmapCacheSize -> preference.summary = - getString(R.string.bitmap_cache_size_summary, value) - PreferKey.imageRetainNum -> preference.summary = - getString(R.string.image_retain_number_summary, value) - - PreferKey.sourceEditMaxLine -> preference.summary = - getString(R.string.source_edit_max_line_summary, value) - - else -> if (preference is ListPreference) { - val index = preference.findIndexOfValue(value) - // Set the summary to reflect the new value. - preference.summary = if (index >= 0) preference.entries[index] else null - } else { - preference.summary = value - } - } - } - - private fun checkPermission(int: Int) { - when (int) { - 1 -> { - PermissionsCompat.Builder() - .addPermissions(Permissions.POST_NOTIFICATIONS) - .rationale(R.string.notification_permission_rationale) - .onGranted { updatePermissionSummary() } - .request() - } - 2 -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - PermissionsCompat.Builder() - .addPermissions(Permissions.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) - .rationale(R.string.ignore_battery_permission_rationale) - .request() - } - } - } - } - - @SuppressLint("InflateParams") - private fun showUserAgentDialog() { - alert(getString(R.string.user_agent)) { - val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply { - editView.hint = getString(R.string.user_agent) - editView.setText(AppConfig.userAgent) - } - customView { alertBinding.root } - okButton { - val userAgent = alertBinding.editView.text?.toString() - if (userAgent.isNullOrBlank()) { - removePref(PreferKey.userAgent) - } else { - putPrefString(PreferKey.userAgent, userAgent) - } - } - cancelButton() - } - } - - private fun clearCache() { - requireContext().alert( - titleResource = R.string.clear_cache, - messageResource = R.string.sure_del - ) { - okButton { - viewModel.clearCache() - } - noButton() - } - } - - private fun shrinkDatabase() { - alert(R.string.sure, R.string.shrink_database) { - okButton { - viewModel.shrinkDatabase() - } - noButton() - } - } - - private fun clearWebViewData() { - alert(R.string.clear_webview_data, R.string.sure_del) { - okButton { - viewModel.clearWebViewData() - } - noButton() - } - } - - private fun isProcessTextEnabled(): Boolean { - return packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED - } - - private fun setProcessTextEnable(enable: Boolean) { - if (enable) { - packageManager.setComponentEnabledSetting( - componentName, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP - ) - } else { - packageManager.setComponentEnabledSetting( - componentName, - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP - ) - } - } - - private fun alertLocalPassword() { - context?.alert(R.string.set_local_password, R.string.set_local_password_summary) { - val editTextBinding = DialogEditTextBinding.inflate(layoutInflater).apply { - editView.hint = "password" - } - customView { - editTextBinding.root - } - okButton { - LocalConfig.password = editTextBinding.editView.text.toString() - } - cancelButton() - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/ReadConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/ReadConfigFragment.kt deleted file mode 100644 index e8677ba4a..000000000 --- a/app/src/main/java/io/legado/app/ui/config/ReadConfigFragment.kt +++ /dev/null @@ -1,175 +0,0 @@ -package io.legado.app.ui.config - -import android.annotation.SuppressLint -import android.content.SharedPreferences -import android.os.Bundle -import android.view.View -import android.view.ViewConfiguration -import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat -import io.legado.app.R -import io.legado.app.constant.EventBus -import io.legado.app.constant.PreferKey -import io.legado.app.help.config.AppConfig -import io.legado.app.help.config.ReadBookConfig -import io.legado.app.model.ReadBook -import io.legado.app.ui.book.read.ReadBookActivity -import io.legado.app.ui.book.read.config.PageKeyDialog -import io.legado.app.ui.book.read.page.provider.ChapterProvider -import io.legado.app.ui.widget.number.NumberPickerDialog -import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory -import io.legado.app.utils.getPrefBoolean -import io.legado.app.utils.postEvent -import io.legado.app.utils.removePref - -class ReadConfigFragment : PreferenceFragmentCompat(), - SharedPreferences.OnSharedPreferenceChangeListener { - - private val slopSquare by lazy { ViewConfiguration.get(requireContext()).scaledTouchSlop } - - @SuppressLint("RestrictedApi") - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.pref_config_read) - upPreferenceSummary(PreferKey.menuAlpha, AppConfig.menuAlpha.toString()) - upPreferenceSummary(PreferKey.pageTouchSlop, slopSquare.toString()) - if (!CanvasRecorderFactory.isSupport) { - removePref(PreferKey.optimizeRender) - preferenceScreen.removePreferenceRecursively(PreferKey.optimizeRender) - } - val pref = findPreference("clickRegionalConfig") - pref?.isEnabled = false - - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - activity?.setTitle(R.string.read) - } - - override fun onResume() { - super.onResume() - preferenceManager - .sharedPreferences - ?.registerOnSharedPreferenceChangeListener(this) - } - - override fun onPause() { - preferenceManager - .sharedPreferences - ?.unregisterOnSharedPreferenceChangeListener(this) - super.onPause() - } - - override fun onSharedPreferenceChanged( - sharedPreferences: SharedPreferences?, - key: String? - ) { - when (key) { - PreferKey.readBodyToLh -> activity?.recreate() - PreferKey.hideStatusBar -> { - ReadBookConfig.hideStatusBar = getPrefBoolean(PreferKey.hideStatusBar) - postEvent(EventBus.UP_CONFIG, arrayListOf(0, 2)) - } - - PreferKey.hideNavigationBar -> { - ReadBookConfig.hideNavigationBar = getPrefBoolean(PreferKey.hideNavigationBar) - postEvent(EventBus.UP_CONFIG, arrayListOf(0, 2)) - } - - PreferKey.keepLight -> postEvent(key, true) - PreferKey.readSliderMode -> postEvent(EventBus.UPDATE_READ_ACTION_BAR, true) - PreferKey.textSelectAble -> postEvent(key, getPrefBoolean(key)) - PreferKey.screenOrientation -> { - (activity as? ReadBookActivity)?.setOrientation() - } - - PreferKey.textFullJustify, - PreferKey.textBottomJustify, - PreferKey.useZhLayout -> { - postEvent(EventBus.UP_CONFIG, arrayListOf(5)) - } - - PreferKey.showBrightnessView -> { - postEvent(PreferKey.showBrightnessView, "") - } - - PreferKey.expandTextMenu -> { - (activity as? ReadBookActivity)?.textActionMenu?.upMenu() - } - - PreferKey.doublePageHorizontal -> { - ChapterProvider.upLayout() - ReadBook.loadContent(false) - } - - PreferKey.showReadTitleAddition, - PreferKey.readBarStyleFollowPage -> { - postEvent(EventBus.UPDATE_READ_ACTION_BAR, true) - } - - PreferKey.progressBarBehavior -> { - postEvent(EventBus.UP_SEEK_BAR, true) - } - - PreferKey.noAnimScrollPage -> { - ReadBook.callBack?.upPageAnim() - } - - PreferKey.optimizeRender -> { - ChapterProvider.upStyle() - ReadBook.callBack?.upPageAnim(true) - ReadBook.loadContent(false) - } - - PreferKey.paddingDisplayCutouts -> { - postEvent(EventBus.UP_CONFIG, arrayListOf(2)) - } - } - } - - override fun onPreferenceTreeClick(preference: Preference): Boolean { - when (preference.key) { - "customPageKey" -> PageKeyDialog(requireContext()).show() - "clickRegionalConfig" -> { - (activity as? ReadBookActivity)?.showClickRegionalConfig() - } - - PreferKey.menuAlpha -> { - NumberPickerDialog(requireContext()) - .setTitle(getString(R.string.menu_alpha)) - .setMaxValue(100) - .setMinValue(0) - .setValue(AppConfig.menuAlpha) - .show { - AppConfig.menuAlpha = it - upPreferenceSummary(PreferKey.menuAlpha, it.toString()) - postEvent(EventBus.UPDATE_READ_ACTION_BAR, true) - } - } - - PreferKey.pageTouchSlop -> { - NumberPickerDialog(requireContext()) - .setTitle(getString(R.string.page_touch_slop_dialog_title)) - .setMaxValue(9999) - .setMinValue(0) - .setValue(AppConfig.pageTouchSlop) - .show { - AppConfig.pageTouchSlop = it - postEvent(EventBus.UP_CONFIG, arrayListOf(4)) - } - } - } - return super.onPreferenceTreeClick(preference) - } - - @Suppress("SameParameterValue") - private fun upPreferenceSummary(preferenceKey: String, value: String?) { - val preference = findPreference(preferenceKey) ?: return - when (preferenceKey) { - PreferKey.menuAlpha -> preference.summary = - getString(R.string.menu_alpha_sum, AppConfig.menuAlpha) - PreferKey.pageTouchSlop -> preference.summary = - getString(R.string.page_touch_slop_summary, value) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/TestConfigActivity.kt b/app/src/main/java/io/legado/app/ui/config/TestConfigActivity.kt deleted file mode 100644 index 8d1642680..000000000 --- a/app/src/main/java/io/legado/app/ui/config/TestConfigActivity.kt +++ /dev/null @@ -1,153 +0,0 @@ -package io.legado.app.ui.config - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import io.legado.app.R -import io.legado.app.base.BaseComposeActivity -import io.legado.app.ui.config.backupConfig.BackupConfigScreen -import io.legado.app.ui.config.coverConfig.CoverConfigScreen -import io.legado.app.ui.config.otherConfig.OtherConfigScreen -import io.legado.app.ui.config.readConfig.ReadConfigScreen -import io.legado.app.ui.config.themeConfig.ThemeConfigScreen -import io.legado.app.ui.widget.components.AppScaffold -import io.legado.app.ui.widget.components.GlassMediumFlexibleTopAppBar -import io.legado.app.ui.widget.components.GlassTopAppBarDefaults -import io.legado.app.ui.widget.components.SplicedColumnGroup -import io.legado.app.ui.widget.components.button.TopbarNavigationButton -import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem -import kotlinx.serialization.Serializable - -class TestConfigActivity : BaseComposeActivity() { - - @Serializable - object ConfigNavRoute - - @Serializable - object OtherConfigRoute - - @Serializable - object ReadConfigRoute - - @Serializable - object CoverConfigRoute - - @Serializable - object ThemeConfigRoute - - @Serializable - object BackupConfigRoute - - @Composable - override fun Content() { - val navController = rememberNavController() - - NavHost( - navController = navController, - startDestination = ConfigNavRoute - ) { - composable { - ConfigNavScreen( - onBackClick = { finish() }, - onNavigateToOther = { navController.navigate(OtherConfigRoute) }, - onNavigateToRead = { navController.navigate(ReadConfigRoute) }, - onNavigateToCover = { navController.navigate(CoverConfigRoute) }, - onNavigateToTheme = { navController.navigate(ThemeConfigRoute) }, - onNavigateToBackup = { navController.navigate(BackupConfigRoute) } - ) - } - - composable { - OtherConfigScreen(onBackClick = { navController.popBackStack() }) - } - - composable { - ReadConfigScreen(onBackClick = { navController.popBackStack() }) - } - - composable { - CoverConfigScreen(onBackClick = { navController.popBackStack() }) - } - - composable { - ThemeConfigScreen( - onBackClick = { navController.popBackStack() } - ) - } - - composable { - BackupConfigScreen(onBackClick = { navController.popBackStack() }) - } - } - } - - @OptIn(ExperimentalMaterial3Api::class) - @Composable - fun ConfigNavScreen( - onBackClick: () -> Unit, - onNavigateToOther: () -> Unit, - onNavigateToRead: () -> Unit, - onNavigateToCover: () -> Unit, - onNavigateToTheme: () -> Unit, - onNavigateToBackup: () -> Unit - ) { - val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() - - AppScaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - GlassMediumFlexibleTopAppBar( - title = { Text("设置") }, - scrollBehavior = scrollBehavior, - navigationIcon = { - TopbarNavigationButton(onClick = onBackClick) - } - ) - } - ) { paddingValues -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .verticalScroll(rememberScrollState()) - .padding(16.dp) - ) { - SplicedColumnGroup { - ClickableSettingItem( - title = stringResource(R.string.theme_setting), - onClick = onNavigateToTheme - ) - ClickableSettingItem( - title = stringResource(R.string.other_setting), - onClick = onNavigateToOther - ) - ClickableSettingItem( - title = stringResource(R.string.read_config), - onClick = onNavigateToRead - ) - ClickableSettingItem( - title = stringResource(R.string.cover_config), - onClick = onNavigateToCover - ) - ClickableSettingItem( - title = stringResource(R.string.backup_restore), - onClick = onNavigateToBackup - ) - } - } - } - } - -} diff --git a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt deleted file mode 100644 index 93b164981..000000000 --- a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt +++ /dev/null @@ -1,479 +0,0 @@ -package io.legado.app.ui.config - -//import io.legado.app.lib.theme.primaryColor -import android.annotation.SuppressLint -import android.app.Application -import android.content.SharedPreferences -import android.net.Uri -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.view.View -import android.widget.SeekBar -import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat -import com.google.android.material.color.DynamicColors -import com.google.android.material.color.DynamicColorsOptions -import io.legado.app.R -import io.legado.app.base.AppContextWrapper -import io.legado.app.constant.EventBus -import io.legado.app.constant.PreferKey -import io.legado.app.databinding.DialogEditTextBinding -import io.legado.app.databinding.DialogImageBlurringBinding -import io.legado.app.help.LauncherIconHelp -import io.legado.app.help.config.AppConfig -import io.legado.app.help.config.OldThemeConfig -import io.legado.app.lib.dialogs.alert -import io.legado.app.lib.dialogs.selector -import io.legado.app.lib.prefs.ColorPreference -import io.legado.app.lib.prefs.ImagePreviewPreference -import io.legado.app.lib.prefs.NameListPreference -import io.legado.app.lib.prefs.ThemeCardPreference -import io.legado.app.lib.prefs.ThemeModePreference -import io.legado.app.lib.theme.ThemeStore -import io.legado.app.lib.theme.primaryColor -import io.legado.app.ui.config.themeConfig.ThemeConfig -import io.legado.app.ui.widget.number.NumberPickerDialog -import io.legado.app.ui.widget.seekbar.SeekBarChangeListener -import io.legado.app.utils.ColorUtils -import io.legado.app.utils.FileUtils -import io.legado.app.utils.MD5Utils -import io.legado.app.utils.SelectImageContract -import io.legado.app.utils.externalFiles -import io.legado.app.utils.getPrefInt -import io.legado.app.utils.getPrefString -import io.legado.app.utils.inputStream -import io.legado.app.utils.postEvent -import io.legado.app.utils.putPrefInt -import io.legado.app.utils.putPrefString -import io.legado.app.utils.readUri -import io.legado.app.utils.removePref -import io.legado.app.utils.restart -import io.legado.app.utils.startActivity -import io.legado.app.utils.toastOnUi -import splitties.init.appCtx -import java.io.FileOutputStream - - -@Suppress("SameParameterValue") -class ThemeConfigFragment : PreferenceFragmentCompat(), - SharedPreferences.OnSharedPreferenceChangeListener - //MenuProvider - { - private val requestCodeBgLight = 121 - private val requestCodeBgDark = 122 - - private val requestCodeColorImage = 123 - - private val selectImage = registerForActivityResult(SelectImageContract()) { - it.uri?.let { uri -> - when (it.requestCode) { - requestCodeBgLight -> setBgFromUri(uri, PreferKey.bgImage) { - upTheme(false) - } - - requestCodeBgDark -> setBgFromUri(uri, PreferKey.bgImageN) { - upTheme(true) - } - - requestCodeColorImage -> setBgFromUri(uri, PreferKey.colorImage) { - - } - } - } - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.pref_config_theme) - - upPreferenceSummary(PreferKey.fontScale) - upPreferenceSummary(PreferKey.containerOpacity) - - findPreference(PreferKey.cBackground)?.let { - it.onSaveColor = { color -> - if (!ColorUtils.isColorLight(color)) { - toastOnUi(R.string.day_background_too_dark) - true - } else { - false - } - } - } - - findPreference(PreferKey.cNBackground)?.let { - it.onSaveColor = { color -> - if (ColorUtils.isColorLight(color)) { - toastOnUi(R.string.night_background_too_light) - true - } else { - false - } - } - } - - findPreference(PreferKey.themeMode)?.let { - it.setOnPreferenceChangeListener { _, _ -> - true - } - } - - val themePref = findPreference(PreferKey.themePref) - val colorImage = findPreference(PreferKey.colorImage) - val colorPrimary = findPreference("colorPrimary") - val customMode = findPreference("customMode") - val currentTheme = getPrefString("app_theme") - - colorPrimary?.isVisible = currentTheme == "12" - colorImage?.isVisible = currentTheme == "12" - customMode?.isVisible = currentTheme == "12" - - themePref?.let { - it.setOnPreferenceChangeListener { _, _ -> - true - } - } - - themePref?.setOnPreferenceChangeListener { _, newValue -> - colorPrimary?.isVisible = newValue == "12" - colorImage?.isVisible = newValue == "12" - customMode?.isVisible = newValue == "12" - true - } - - // TODO:删掉种子色了喵 - val hasColorImage = !getPrefString(PreferKey.colorImage).isNullOrBlank() - colorPrimary?.isEnabled = !hasColorImage - if (hasColorImage) - { - upPreferenceSummary("colorPrimary", getString(R.string.seed_photo_alart)) - upPreferenceSummary(PreferKey.colorImage, getString(R.string.click_to_delete)) - } - upPreferenceSummary(PreferKey.bgImage) - upPreferenceSummary(PreferKey.bgImageN) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - activity?.setTitle(R.string.theme_setting) - //listView.setEdgeEffectColor(primaryColor) - //activity?.addMenuProvider(this, viewLifecycleOwner) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) - } - - override fun onDestroy() { - super.onDestroy() - preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - sharedPreferences ?: return - when (key) { - PreferKey.launcherIcon -> LauncherIconHelp.changeIcon(getPrefString(key)) - //PreferKey.transparentStatusBar -> recreateActivities() - //PreferKey.immNavigationBar -> recreateActivities() - - PreferKey.themePref -> { - //recreateActivities() - } - - - PreferKey.paletteStyle, PreferKey.pureBlack, PreferKey.swipeAnimation -> { - Handler(Looper.getMainLooper()).postDelayed({ - recreateActivities() - }, 100) - } - - PreferKey.enableBlur -> { - ThemeConfig.enableBlur = PreferKey.enableBlur.toBoolean() - } - - PreferKey.enableProgressiveBlur -> { - ThemeConfig.enableProgressiveBlur = PreferKey.enableProgressiveBlur.toBoolean() - } - - PreferKey.customMode -> handleRestartRequired() - - PreferKey.isPredictiveBackEnabled -> { - toastOnUi("重启以应用") - } - - PreferKey.cPrimary -> { - val color = getPrefInt(key, ThemeStore.primaryColor(requireContext())) - ThemeStore.editTheme(requireContext()) - .primaryColor(color) - .apply() - DynamicColors.applyToActivitiesIfAvailable(requireContext().applicationContext as Application, DynamicColorsOptions.Builder() - .setContentBasedSource(requireContext().primaryColor) - .build()) - Handler(Looper.getMainLooper()).postDelayed({ - recreateActivities() - }, 100) - } - - PreferKey.cBackground, - PreferKey.cBBackground -> { - upTheme(false) - } - - //PreferKey.cNPrimary, - - PreferKey.cNBackground, - PreferKey.cNBBackground -> { - upTheme(true) - } - - PreferKey.themeMode -> { - //recreateActivities() - } - - PreferKey.bgImage, - PreferKey.bgImageN -> { - upPreferenceSummary(key, getString(R.string.click_to_delete)) - } - - PreferKey.colorImage -> handleRestartRequired() - - PreferKey.showDiscovery, PreferKey.showRss, - PreferKey.showStatusBar, PreferKey.showBottomView, PreferKey.tabletInterface, - PreferKey.labelVisibilityMode -> postEvent(EventBus.NOTIFY_MAIN, true) - } - - } - - @SuppressLint("PrivateResource") - override fun onPreferenceTreeClick(preference: Preference): Boolean { - when (val key = preference.key) { - - PreferKey.fontScale -> NumberPickerDialog(requireContext()) - .setTitle(getString(R.string.font_scale)) - .setMaxValue(16) - .setMinValue(8) - .setValue(10) - .setCustomButton((R.string.btn_default_s)) { - putPrefInt(PreferKey.fontScale, 0) - Handler(Looper.getMainLooper()).postDelayed({ - recreateActivities() - }, 100) - } - .show { - putPrefInt(PreferKey.fontScale, it) - Handler(Looper.getMainLooper()).postDelayed({ - recreateActivities() - }, 100) - } - - PreferKey.containerOpacity -> NumberPickerDialog(requireContext()) - .setTitle(getString(R.string.container_opacity)) - .setMaxValue(100) - .setMinValue(0) - .setValue(ThemeConfig.containerOpacity) - .setCustomButton((R.string.btn_default_s)) { - ThemeConfig.containerOpacity = 100 - Handler(Looper.getMainLooper()).postDelayed({ - upPreferenceSummary(PreferKey.containerOpacity) - }, 100) - } - .show { - ThemeConfig.containerOpacity = it - Handler(Looper.getMainLooper()).postDelayed({ - upPreferenceSummary(PreferKey.containerOpacity) - }, 100) - } - - PreferKey.bgImage -> selectBgAction(false) - PreferKey.bgImageN -> selectBgAction(true) - "colorImage" -> selectBgAction(null) - - "themeList" -> ThemeListDialog().show(childFragmentManager, "themeList") - "saveDayTheme", - "saveNightTheme" -> alertSaveTheme(key) - - "coverConfig" -> startActivity { - putExtra("configTag", ConfigTag.COVER_CONFIG) - } - } - return super.onPreferenceTreeClick(preference) - } - - @SuppressLint("InflateParams") - private fun alertSaveTheme(key: String) { - alert(R.string.theme_name) { - val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply { - editView.hint = "name" - } - customView { alertBinding.root } - okButton { - alertBinding.editView.text?.toString()?.let { themeName -> - when (key) { - "saveDayTheme" -> { - OldThemeConfig.saveDayTheme(requireContext(), themeName) - } - - "saveNightTheme" -> { - OldThemeConfig.saveNightTheme(requireContext(), themeName) - } - } - } - } - cancelButton() - } - } - - private fun selectBgAction(isNight: Boolean?) { - val bgKey = when (isNight) { - true -> PreferKey.bgImageN - false -> PreferKey.bgImage - null -> PreferKey.colorImage - } - val blurringKey = when (isNight) { - true -> PreferKey.bgImageNBlurring - false -> PreferKey.bgImageBlurring - else -> null - } - - val actions = mutableListOf() - - if (isNight != null) { - actions.add(getString(R.string.background_image_blurring)) - } - - actions.add(getString(R.string.select_image)) - - if (!getPrefString(bgKey).isNullOrEmpty()) { - actions.add(getString(R.string.delete)) - } - - context?.selector(items = actions) { _, i -> - when { - isNight != null && i == 0 -> { - alertImageBlurring(blurringKey!!) { - upTheme(isNight) - } - } - - (isNight == null && i == 0) || (isNight != null && i == 1) -> { - when (isNight) { - true -> selectImage.launch(requestCodeBgDark) - false -> selectImage.launch(requestCodeBgLight) - null -> selectImage.launch(requestCodeColorImage) - } - } - - (isNight == null && i == 1) || (isNight != null && i == 2) -> { - removePref(bgKey) - if (isNight != null) { - upTheme(isNight) - } - } - } - } - } - - private fun handleRestartRequired() { - alert(getString(R.string.restart_required_message)) { - okButton { - Handler(Looper.getMainLooper()).postDelayed({ - requireContext().restart() - }, 100) - } - cancelButton { - toastOnUi(R.string.restart_later_message) - } - } - } - - private fun alertImageBlurring(preferKey: String, success: () -> Unit) { - alert(R.string.background_image_blurring) { - val alertBinding = DialogImageBlurringBinding.inflate(layoutInflater).apply { - getPrefInt(preferKey, 0).let { - seekBar.progress = it - textViewValue.text = it.toString() - } - seekBar.setOnSeekBarChangeListener(object : SeekBarChangeListener { - override fun onProgressChanged( - seekBar: SeekBar, - progress: Int, - fromUser: Boolean - ) { - textViewValue.text = progress.toString() - } - }) - } - customView { alertBinding.root } - okButton { - alertBinding.seekBar.progress.let { - putPrefInt(preferKey, it) - success.invoke() - } - } - cancelButton() - } - } - - private fun upTheme(isNightTheme: Boolean) { - if (AppConfig.isNightTheme == isNightTheme) { - listView.post { - //ThemeConfig.applyTheme(requireContext()) - recreateActivities() - } - } - } - - private fun recreateActivities() { - postEvent(EventBus.RECREATE, "") - } - - private fun upPreferenceSummary(preferenceKey: String, value: String? = null) { - val preference = findPreference(preferenceKey) ?: return - when (preferenceKey) { - //PreferKey.barElevation -> preference.summary = - // getString(R.string.bar_elevation_s, value) - PreferKey.containerOpacity -> { - preference.summary = - getString(R.string.container_opacity_summary, AppConfig.containerOpacity) - } - - PreferKey.fontScale -> { - val fontScale = AppContextWrapper.getFontScale(requireContext()) - preference.summary = getString(R.string.font_scale_summary, fontScale) - } - - PreferKey.bgImage, - PreferKey.bgImageN -> { - val hasImage = !getPrefString(preferenceKey).isNullOrBlank() - preference.summary = if (hasImage) { - getString(R.string.click_to_delete) - } else { - getString(R.string.select_image) - } - } - - else -> preference.summary = value - } - } - - private fun setBgFromUri(uri: Uri, preferenceKey: String, success: () -> Unit) { - readUri(uri) { fileDoc, inputStream -> - kotlin.runCatching { - var file = requireContext().externalFiles - val suffix = fileDoc.name.substringAfterLast(".") - val fileName = uri.inputStream(requireContext()).getOrThrow().use { - MD5Utils.md5Encode(it) + ".$suffix" - } - file = FileUtils.createFileIfNotExist(file, preferenceKey, fileName) - FileOutputStream(file).use { - inputStream.copyTo(it) - } - putPrefString(preferenceKey, file.absolutePath) - success() - }.onFailure { - appCtx.toastOnUi(it.localizedMessage) - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/ThemeListDialog.kt b/app/src/main/java/io/legado/app/ui/config/ThemeListDialog.kt deleted file mode 100644 index 804eab7ac..000000000 --- a/app/src/main/java/io/legado/app/ui/config/ThemeListDialog.kt +++ /dev/null @@ -1,129 +0,0 @@ -package io.legado.app.ui.config - -import android.content.Context -import android.os.Bundle -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.widget.Toolbar -import androidx.core.graphics.toColorInt -import androidx.recyclerview.widget.LinearLayoutManager -import io.legado.app.R -import io.legado.app.base.BaseBottomSheetDialogFragment -import io.legado.app.base.adapter.ItemViewHolder -import io.legado.app.base.adapter.RecyclerAdapter -import io.legado.app.databinding.DialogRecyclerViewBinding -import io.legado.app.databinding.ItemThemeConfigBinding -import io.legado.app.help.config.OldThemeConfig -import io.legado.app.lib.dialogs.alert -import io.legado.app.lib.theme.primaryColor -//import io.legado.app.lib.theme.primaryColor -import io.legado.app.utils.* -import io.legado.app.utils.viewbindingdelegate.viewBinding - -class ThemeListDialog : BaseBottomSheetDialogFragment(R.layout.dialog_recycler_view), - Toolbar.OnMenuItemClickListener { - - private val binding by viewBinding(DialogRecyclerViewBinding::bind) - private val adapter by lazy { Adapter(requireContext()) } - - override fun onStart() { - super.onStart() - //setLayout(0.9f, 0.9f) - } - - override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { - //binding.toolBar.setBackgroundColor(primaryColor) - binding.toolBar.setTitle(R.string.theme_list) - initView() - initMenu() - initData() - } - - private fun initView() = binding.run { - recyclerView.layoutManager = LinearLayoutManager(requireContext()) - recyclerView.adapter = adapter - } - - private fun initMenu() = binding.run { - toolBar.setOnMenuItemClickListener(this@ThemeListDialog) - toolBar.inflateMenu(R.menu.theme_list) - toolBar.menu.applyTint(requireContext()) - } - - fun initData() { - adapter.setItems(OldThemeConfig.configList) - } - - override fun onMenuItemClick(item: MenuItem?): Boolean { - when (item?.itemId) { - R.id.menu_import -> { - requireContext().getClipText()?.let { - if (OldThemeConfig.addConfig(it)) { - initData() - } else { - toastOnUi("格式不对,添加失败") - } - } - } - } - return true - } - - fun delete(index: Int) { - alert(R.string.delete, R.string.sure_del) { - yesButton { - OldThemeConfig.delConfig(index) - initData() - } - noButton() - } - } - - fun share(index: Int) { - val json = GSON.toJson(OldThemeConfig.configList[index]) - requireContext().share(json, "主题分享") - } - - inner class Adapter(context: Context) : - RecyclerAdapter(context) { - - override fun getViewBinding(parent: ViewGroup): ItemThemeConfigBinding { - return ItemThemeConfigBinding.inflate(inflater, parent, false) - } - - override fun convert( - holder: ItemViewHolder, - binding: ItemThemeConfigBinding, - item: OldThemeConfig.Config, - payloads: MutableList - ) { - binding.apply { - tvName.text = item.themeName - if (item.primaryColor.toColorInt() == context.primaryColor) - cdRoot.setCardBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSecondaryContainer)) - else - cdRoot.setCardBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSurfaceContainer)) - } - } - - - override fun registerListener(holder: ItemViewHolder, binding: ItemThemeConfigBinding) { - binding.apply { - root.setOnClickListener { - OldThemeConfig.applyConfig( - context, - OldThemeConfig.configList[holder.layoutPosition] - ) - } - ivShare.setOnClickListener { - share(holder.layoutPosition) - } - ivDelete.setOnClickListener { - delete(holder.layoutPosition) - } - } - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigViewModel.kt b/app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigViewModel.kt index 9a260ed41..1fcf351e7 100644 --- a/app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigViewModel.kt @@ -11,6 +11,12 @@ import splitties.init.appCtx class BackupConfigViewModel : ViewModel() { + private suspend fun syncWebDavConfig() { + withContext(Dispatchers.IO) { + AppWebDav.upConfig() + } + } + fun setWebDavAccount(account: String, password: String) { BackupConfig.webDavAccount = account BackupConfig.webDavPassword = password @@ -25,6 +31,7 @@ class BackupConfigViewModel : ViewModel() { } suspend fun testWebDav(): Boolean { + syncWebDavConfig() return withContext(Dispatchers.IO) { try { AppWebDav.testWebDav() @@ -51,6 +58,7 @@ class BackupConfigViewModel : ViewModel() { } suspend fun getBackupNames(): List { + syncWebDavConfig() return withContext(Dispatchers.IO) { AppWebDav.getBackupNames() } @@ -59,6 +67,7 @@ class BackupConfigViewModel : ViewModel() { fun restoreWebDav(name: String, onSuccess: () -> Unit, onError: (String) -> Unit) { viewModelScope.launch(Dispatchers.IO) { try { + AppWebDav.upConfig() AppWebDav.restoreWebDav(name) withContext(Dispatchers.Main) { onSuccess() diff --git a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt index 8554a77d0..584cf9ce8 100644 --- a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt +++ b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt @@ -42,6 +42,7 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.ToggleButton @@ -108,6 +109,9 @@ fun ThemeConfigScreen( var showRestartDialog by remember { mutableStateOf(false) } var showColorPicker by remember { mutableStateOf(false) } var showLauncherIconPicker by remember { mutableStateOf(false) } + var showThemeListDialog by remember { mutableStateOf(false) } + var saveThemeKey by remember { mutableStateOf(null) } + var themeName by remember { mutableStateOf("") } val fontScaleValue = remember { mutableFloatStateOf(ThemeConfig.fontScale.toFloat()) } val primaryColorValue = remember { mutableIntStateOf(ThemeConfig.cPrimary) } @@ -339,6 +343,22 @@ fun ThemeConfigScreen( ) } + SplicedColumnGroup(title = stringResource(R.string.share)) { + ClickableSettingItem( + title = stringResource(R.string.theme_list), + description = stringResource(R.string.theme_list_summary), + onClick = { showThemeListDialog = true } + ) + ClickableSettingItem( + title = stringResource(R.string.save_theme_config), + description = stringResource(R.string.save_day_theme_summary), + onClick = { + saveThemeKey = "saveDayTheme" + themeName = "" + } + ) + } + SplicedColumnGroup(title = stringResource(R.string.compose_related)) { SwitchSettingItem( title = stringResource(R.string.use_flexible_top_bar), @@ -483,6 +503,45 @@ fun ThemeConfigScreen( } ) } + + if (showThemeListDialog) { + ThemeListDialog( + onDismissRequest = { showThemeListDialog = false } + ) + } + + saveThemeKey?.let { key -> + AlertDialog( + onDismissRequest = { saveThemeKey = null }, + title = { Text(stringResource(R.string.theme_name)) }, + text = { + OutlinedTextField( + value = themeName, + onValueChange = { themeName = it }, + label = { Text("name") }, + singleLine = true + ) + }, + confirmButton = { + TextButton( + onClick = { + when (key) { + "saveDayTheme" -> OldThemeConfig.saveDayTheme(context, themeName) + "saveNightTheme" -> OldThemeConfig.saveNightTheme(context, themeName) + } + saveThemeKey = null + } + ) { + Text(stringResource(android.R.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = { saveThemeKey = null }) { + Text(stringResource(android.R.string.cancel)) + } + } + ) + } } @OptIn(ExperimentalMaterial3ExpressiveApi::class) diff --git a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeListDialog.kt b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeListDialog.kt new file mode 100644 index 000000000..da96e5864 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeListDialog.kt @@ -0,0 +1,184 @@ +package io.legado.app.ui.config.themeConfig + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.FileDownload +import androidx.compose.material.icons.filled.Share +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.core.graphics.toColorInt +import io.legado.app.R +import io.legado.app.help.config.OldThemeConfig +import io.legado.app.lib.theme.primaryColor +import io.legado.app.ui.widget.components.card.GlassCard +import io.legado.app.ui.widget.components.modalBottomSheet.GlassModalBottomSheet +import io.legado.app.utils.GSON +import io.legado.app.utils.getClipText +import io.legado.app.utils.share +import io.legado.app.utils.toastOnUi + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ThemeListDialog( + onDismissRequest: () -> Unit +) { + val context = LocalContext.current + var listVersion by remember { mutableIntStateOf(0) } + var deleteIndex by remember { mutableStateOf(null) } + val themeList = remember(listVersion) { OldThemeConfig.configList.toList() } + + GlassModalBottomSheet( + onDismissRequest = onDismissRequest + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 24.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 8.dp, top = 8.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.theme_list), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.weight(1f) + ) + IconButton( + onClick = { + val clipText = context.getClipText() + if (clipText != null && OldThemeConfig.addConfig(clipText)) { + listVersion++ + } else { + context.toastOnUi("Import failed") + } + } + ) { + Icon( + imageVector = Icons.Default.FileDownload, + contentDescription = stringResource(R.string.import_theme) + ) + } + } + + LazyColumn( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + itemsIndexed(themeList, key = { _, item -> item.themeName }) { index, item -> + GlassCard( + onClick = { + OldThemeConfig.applyConfig(context, item) + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + shape = RoundedCornerShape(20.dp), + colors = CardDefaults.cardColors( + containerColor = if (item.primaryColor.toColorInt() == context.primaryColor) { + MaterialTheme.colorScheme.secondaryContainer + } else { + MaterialTheme.colorScheme.surfaceContainer + } + ) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 8.dp, top = 12.dp, bottom = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .padding(end = 12.dp) + .background( + color = Color( + item.primaryColor.toColorInt() + ), + shape = MaterialTheme.shapes.large + ) + .padding(8.dp) + ) + Text( + text = item.themeName, + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.weight(1f) + ) + IconButton( + onClick = { + val json = GSON.toJson(item) + context.share(json, "主题分享") + } + ) { + Icon( + imageVector = Icons.Default.Share, + contentDescription = stringResource(R.string.share) + ) + } + IconButton(onClick = { deleteIndex = index }) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = stringResource(R.string.delete) + ) + } + } + } + } + } + } + } + + deleteIndex?.let { index -> + AlertDialog( + onDismissRequest = { deleteIndex = null }, + title = { Text(stringResource(R.string.delete)) }, + text = { Text(stringResource(R.string.sure_del)) }, + confirmButton = { + TextButton( + onClick = { + OldThemeConfig.delConfig(index) + listVersion++ + deleteIndex = null + } + ) { + Text(stringResource(android.R.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = { deleteIndex = null }) { + Text(stringResource(android.R.string.cancel)) + } + } + ) + } +} 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 b1ef9a1d9..e1669dbed 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 @@ -1,11 +1,27 @@ package io.legado.app.ui.main +import android.content.Context +import android.content.Intent import android.content.res.Configuration import android.os.Bundle import android.text.format.DateUtils +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleOut +import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.lifecycleScope +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.ui.NavDisplay import io.legado.app.BuildConfig import io.legado.app.R import io.legado.app.base.BaseComposeActivity @@ -23,15 +39,24 @@ 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 +import io.legado.app.ui.config.ConfigNavScreen +import io.legado.app.ui.config.ConfigTag +import io.legado.app.ui.config.backupConfig.BackupConfigScreen +import io.legado.app.ui.config.coverConfig.CoverConfigScreen import io.legado.app.ui.config.mainConfig.MainConfig +import io.legado.app.ui.config.otherConfig.OtherConfigScreen +import io.legado.app.ui.config.readConfig.ReadConfigScreen +import io.legado.app.ui.config.themeConfig.ThemeConfigScreen import io.legado.app.ui.welcome.WelcomeActivity import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.showDialogFragment import io.legado.app.utils.startActivity import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.serialization.Serializable import org.koin.androidx.viewmodel.ext.android.viewModel import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -41,7 +66,60 @@ import kotlin.coroutines.suspendCoroutine */ open class MainActivity : BaseComposeActivity() { + companion object { + const val EXTRA_START_ROUTE = "startRoute" + private const val ROUTE_MAIN = "main" + private const val ROUTE_SETTINGS = "settings" + private const val ROUTE_SETTINGS_OTHER = "settings/other" + private const val ROUTE_SETTINGS_READ = "settings/read" + private const val ROUTE_SETTINGS_COVER = "settings/cover" + private const val ROUTE_SETTINGS_THEME = "settings/theme" + private const val ROUTE_SETTINGS_BACKUP = "settings/backup" + + fun createIntent(context: Context, configTag: String? = null): Intent { + return Intent(context, MainActivity::class.java).apply { + putExtra(EXTRA_START_ROUTE, routeForConfigTag(configTag)) + } + } + + private fun routeForConfigTag(configTag: String?): String { + return when (configTag) { + ConfigTag.OTHER_CONFIG -> ROUTE_SETTINGS_OTHER + ConfigTag.READ_CONFIG -> ROUTE_SETTINGS_READ + ConfigTag.COVER_CONFIG -> ROUTE_SETTINGS_COVER + ConfigTag.THEME_CONFIG -> ROUTE_SETTINGS_THEME + ConfigTag.BACKUP_CONFIG -> ROUTE_SETTINGS_BACKUP + else -> ROUTE_SETTINGS + } + } + } + private val viewModel by viewModel() + private val routeEvents = MutableSharedFlow(extraBufferCapacity = 1) + + @Serializable + private sealed interface MainRoute : NavKey + + @Serializable + private data object MainRouteHome : MainRoute + + @Serializable + private data object MainRouteSettings : MainRoute + + @Serializable + private data object MainRouteSettingsOther : MainRoute + + @Serializable + private data object MainRouteSettingsRead : MainRoute + + @Serializable + private data object MainRouteSettingsCover : MainRoute + + @Serializable + private data object MainRouteSettingsTheme : MainRoute + + @Serializable + private data object MainRouteSettingsBackup : MainRoute override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() @@ -70,6 +148,12 @@ open class MainActivity : BaseComposeActivity() { } } + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + routeEvents.tryEmit(resolveStartRoute(intent)) + } + @Composable override fun Content() { val orientation = resources.configuration.orientation @@ -84,7 +168,169 @@ open class MainActivity : BaseComposeActivity() { else -> false } - MainScreen(useRail = useRail) + val backStack = rememberNavBackStack(resolveStartRoute(intent)) + + LaunchedEffect(backStack) { + routeEvents.collect { route -> + navigateToRoute(backStack, route) + } + } + + NavDisplay( + backStack = backStack, + transitionSpec = { + (slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Start, + animationSpec = tween( + durationMillis = 260, + easing = FastOutSlowInEasing + ), + initialOffset = { fullWidth -> fullWidth / 10 } + ) + fadeIn( + animationSpec = tween( + durationMillis = 220, + easing = LinearOutSlowInEasing + ) + )) togetherWith ExitTransition.None + }, + popTransitionSpec = { + (slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Start, + animationSpec = tween( + durationMillis = 240, + easing = FastOutSlowInEasing + ), + initialOffset = { fullWidth -> -fullWidth / 8 } + ) + fadeIn( + animationSpec = tween( + durationMillis = 200, + easing = LinearOutSlowInEasing + ) + )) togetherWith + (scaleOut( + targetScale = 0.92f, + animationSpec = tween( + durationMillis = 220, + easing = FastOutSlowInEasing + ) + ) + fadeOut( + animationSpec = tween(durationMillis = 180) + )) + }, + predictivePopTransitionSpec = { _ -> + (slideIntoContainer( + towards = AnimatedContentTransitionScope.SlideDirection.Start, + animationSpec = tween( + durationMillis = 240, + easing = FastOutSlowInEasing + ), + initialOffset = { fullWidth -> -fullWidth / 8 } + ) + fadeIn( + animationSpec = tween( + durationMillis = 200, + easing = LinearOutSlowInEasing + ) + )) togetherWith + (scaleOut( + targetScale = 0.92f, + animationSpec = tween( + durationMillis = 220, + easing = FastOutSlowInEasing + ) + ) + fadeOut( + animationSpec = tween(durationMillis = 180) + )) + }, + onBack = { + if (backStack.size > 1) { + backStack.removeLastOrNull() + } else { + finish() + } + }, + entryProvider = entryProvider { + entry { + MainScreen( + useRail = useRail, + onOpenSettings = { + navigateToRoute(backStack, MainRouteSettings) + } + ) + } + + entry { + ConfigNavScreen( + onBackClick = { navigateBack(backStack) }, + onNavigateToOther = { backStack.add(MainRouteSettingsOther) }, + onNavigateToRead = { backStack.add(MainRouteSettingsRead) }, + onNavigateToCover = { backStack.add(MainRouteSettingsCover) }, + onNavigateToTheme = { backStack.add(MainRouteSettingsTheme) }, + onNavigateToBackup = { backStack.add(MainRouteSettingsBackup) } + ) + } + + entry { + OtherConfigScreen(onBackClick = { navigateBack(backStack) }) + } + + entry { + ReadConfigScreen(onBackClick = { navigateBack(backStack) }) + } + + entry { + CoverConfigScreen(onBackClick = { navigateBack(backStack) }) + } + + entry { + ThemeConfigScreen(onBackClick = { navigateBack(backStack) }) + } + + entry { + BackupConfigScreen(onBackClick = { navigateBack(backStack) }) + } + } + ) + } + + private fun navigateToRoute(backStack: MutableList, route: MainRoute) { + val currentRoute = backStack.lastOrNull() + if (currentRoute == route) return + + when (route) { + MainRouteHome -> { + backStack.clear() + backStack.add(MainRouteHome) + } + + MainRouteSettings -> { + if (currentRoute == MainRouteHome) { + backStack.add(MainRouteSettings) + } else { + backStack.clear() + backStack.add(MainRouteHome) + backStack.add(MainRouteSettings) + } + } + + MainRouteSettingsOther, + MainRouteSettingsRead, + MainRouteSettingsCover, + MainRouteSettingsTheme, + MainRouteSettingsBackup -> { + backStack.clear() + backStack.add(MainRouteHome) + backStack.add(MainRouteSettings) + backStack.add(route) + } + } + } + + private fun navigateBack(backStack: MutableList) { + if (backStack.size > 1) { + backStack.removeLastOrNull() + } else { + finish() + } } private fun checkStartupRoute(): Boolean { @@ -197,6 +443,23 @@ open class MainActivity : BaseComposeActivity() { } } + private fun resolveStartRoute(intent: Intent?): MainRoute { + return resolveStartRoute(intent?.getStringExtra(EXTRA_START_ROUTE)) + } + + private fun resolveStartRoute(route: String?): MainRoute { + return when (route) { + "main" -> MainRouteHome + "settings" -> MainRouteSettings + "settings/other" -> MainRouteSettingsOther + "settings/read" -> MainRouteSettingsRead + "settings/cover" -> MainRouteSettingsCover + "settings/theme" -> MainRouteSettingsTheme + "settings/backup" -> MainRouteSettingsBackup + else -> MainRouteHome + } + } + } class LauncherW : MainActivity() diff --git a/app/src/main/java/io/legado/app/ui/main/MainScreen.kt b/app/src/main/java/io/legado/app/ui/main/MainScreen.kt index 80359bc81..d92054be6 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainScreen.kt @@ -78,7 +78,8 @@ import org.koin.androidx.compose.koinViewModel @Composable fun MainScreen( viewModel: MainViewModel = koinViewModel(), - useRail: Boolean + useRail: Boolean, + onOpenSettings: () -> Unit ) { val context = LocalContext.current val uiState by viewModel.state.collectAsStateWithLifecycle() @@ -362,6 +363,7 @@ fun MainScreen( MainDestination.Rss -> RssScreen() MainDestination.My -> MyScreen( viewModel = koinViewModel(), + onOpenSettings = onOpenSettings, onNavigate = { event -> viewModel.onPrefClickEvent(context, event) } diff --git a/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt b/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt index ce91c522b..d23e1a50b 100644 --- a/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt @@ -15,13 +15,11 @@ import androidx.compose.material.icons.automirrored.filled.ExitToApp import androidx.compose.material.icons.automirrored.filled.HelpOutline import androidx.compose.material.icons.automirrored.filled.LibraryBooks import androidx.compose.material.icons.automirrored.filled.Rule -import androidx.compose.material.icons.filled.Backup import androidx.compose.material.icons.filled.Bookmark import androidx.compose.material.icons.filled.FindReplace import androidx.compose.material.icons.filled.Folder import androidx.compose.material.icons.filled.History import androidx.compose.material.icons.filled.Info -import androidx.compose.material.icons.filled.Palette import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Source import androidx.compose.material.icons.filled.Web @@ -45,8 +43,6 @@ import io.legado.app.ui.book.bookmark.AllBookmarkActivity import io.legado.app.ui.book.readRecord.ReadRecordActivity import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity -import io.legado.app.ui.config.ConfigActivity -import io.legado.app.ui.config.ConfigTag import io.legado.app.ui.dict.rule.DictRuleActivity import io.legado.app.ui.file.FileManageActivity import io.legado.app.ui.replace.ReplaceRuleActivity @@ -61,6 +57,7 @@ import io.legado.app.ui.widget.components.settingItem.SettingItem @Composable fun MyScreen( viewModel: MyViewModel, + onOpenSettings: () -> Unit, onNavigate: (PrefClickEvent) -> Unit ) { @@ -91,27 +88,20 @@ fun MyScreen( ) { padding -> Column( modifier = Modifier - .padding( - top = padding.calculateTopPadding() - ) .verticalScroll(rememberScrollState()), //TODO:暂时这样解决,改天加模糊 ) { + Spacer( + modifier = Modifier + .padding( + top = padding.calculateTopPadding() + ) + ) SplicedColumnGroup( modifier = Modifier.padding(horizontal = 16.dp), title = "" ) { - SettingItem( - title = stringResource(R.string.book_source_manage), - description = stringResource(R.string.book_source_manage_desc), - imageVector = Icons.Default.Source, - onClick = { - onNavigate( - PrefClickEvent.StartActivity(BookSourceActivity::class.java) - ) - } - ) SettingItem( title = stringResource(R.string.web_service), description = if (uiState.isWebServiceRun) @@ -149,66 +139,21 @@ fun MyScreen( ) } - SplicedColumnGroup( - modifier = Modifier.padding(horizontal = 16.dp), - title = stringResource(R.string.setting)) { - SettingItem( - title = stringResource(R.string.backup_restore), - description = stringResource(R.string.web_dav_set_import_old), - imageVector = Icons.Default.Backup, - onClick = { - onNavigate( - PrefClickEvent.StartActivity( - ConfigActivity::class.java, - ConfigTag.BACKUP_CONFIG - ) - ) - } - ) - SettingItem( - title = stringResource(R.string.theme_setting), - description = stringResource(R.string.theme_setting_s), - imageVector = Icons.Default.Palette, - onClick = { - onNavigate( - PrefClickEvent.StartActivity( - ConfigActivity::class.java, - ConfigTag.THEME_CONFIG - ) - ) - } - ) - SettingItem( - title = stringResource(R.string.read), - description = stringResource(R.string.global_read_setting), - imageVector = Icons.AutoMirrored.Filled.LibraryBooks, - onClick = { - onNavigate( - PrefClickEvent.StartActivity( - ConfigActivity::class.java, - ConfigTag.READ_CONFIG - ) - ) - } - ) - SettingItem( - title = stringResource(R.string.other_setting), - description = stringResource(R.string.other_setting_s), - imageVector = Icons.Default.Settings, - onClick = { - onNavigate( - PrefClickEvent.StartActivity( - ConfigActivity::class.java, - ConfigTag.OTHER_CONFIG - ) - ) - } - ) - } + SplicedColumnGroup( modifier = Modifier.padding(horizontal = 16.dp), title = "规则" ) { + SettingItem( + title = stringResource(R.string.book_source_manage), + description = stringResource(R.string.book_source_manage_desc), + imageVector = Icons.Default.Source, + onClick = { + onNavigate( + PrefClickEvent.StartActivity(BookSourceActivity::class.java) + ) + } + ) SettingItem( title = stringResource(R.string.replace_purify), imageVector = Icons.Default.FindReplace, @@ -229,7 +174,7 @@ fun MyScreen( ) SettingItem( title = stringResource(R.string.dict_rule), - imageVector = Icons.AutoMirrored.Filled.Rule, + imageVector = Icons.AutoMirrored.Filled.LibraryBooks, onClick = { onNavigate( PrefClickEvent.StartActivity(DictRuleActivity::class.java) @@ -237,9 +182,17 @@ fun MyScreen( } ) } + SplicedColumnGroup( modifier = Modifier.padding(horizontal = 16.dp), title = stringResource(R.string.other)) { + SettingItem( + title = stringResource(R.string.setting), + imageVector = Icons.Default.Settings, + onClick = { + onOpenSettings() + } + ) SettingItem( title = stringResource(R.string.bookmark), imageVector = Icons.Default.Bookmark,