Просмотр исходного кода

[优化] 导航系统换成Navigation3,老旧设置页替换为新的Compose界面

HapeLee 7 месяцев назад
Родитель
Сommit
75a3752079
20 измененных файлов с 628 добавлено и 2128 удалено
  1. 5 20
      README.md
  2. 1 10
      app/src/main/AndroidManifest.xml
  3. 1 0
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  4. 1 10
      app/src/main/java/io/legado/app/help/config/OldThemeConfig.kt
  5. 0 414
      app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt
  6. 0 46
      app/src/main/java/io/legado/app/ui/config/ConfigActivity.kt
  7. 78 0
      app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt
  8. 0 51
      app/src/main/java/io/legado/app/ui/config/ConfigViewModel.kt
  9. 0 168
      app/src/main/java/io/legado/app/ui/config/CoverConfigFragment.kt
  10. 0 401
      app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt
  11. 0 175
      app/src/main/java/io/legado/app/ui/config/ReadConfigFragment.kt
  12. 0 153
      app/src/main/java/io/legado/app/ui/config/TestConfigActivity.kt
  13. 0 479
      app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt
  14. 0 129
      app/src/main/java/io/legado/app/ui/config/ThemeListDialog.kt
  15. 9 0
      app/src/main/java/io/legado/app/ui/config/backupConfig/BackupConfigViewModel.kt
  16. 59 0
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt
  17. 184 0
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeListDialog.kt
  18. 264 1
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  19. 3 1
      app/src/main/java/io/legado/app/ui/main/MainScreen.kt
  20. 23 70
      app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt

+ 5 - 20
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(阅读)** 的非官方分支,仅用于学习、交流与个人使用。
 项目内容不代表原作者立场,与原项目作者不存在任何隶属或授权关系。
 

+ 1 - 10
app/src/main/AndroidManifest.xml

@@ -211,15 +211,6 @@
             android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
             android:launchMode="singleTask"
             android:windowSoftInputMode="adjustResize|stateHidden" />
-        <!-- 配置界面 -->
-        <activity
-            android:name=".ui.config.ConfigActivity"
-            android:enableOnBackInvokedCallback="true"
-            android:launchMode="standard" />
-        <activity
-            android:name=".ui.config.TestConfigActivity"
-            android:enableOnBackInvokedCallback="true"
-            android:launchMode="standard" />
         <!-- 搜索界面 -->
         <activity
             android:name=".ui.book.search.SearchActivity"
@@ -595,4 +586,4 @@
         </intent>
     </queries>
 
-</manifest>
+</manifest>

+ 1 - 0
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"

+ 1 - 10
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 {
 
     }
 
-}
+}

+ 0 - 414
app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt

@@ -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<ConfigViewModel>()
-    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<EditTextPreference>(PreferKey.webDavDir)?.let {
-            it.setOnBindEditTextListener { editText ->
-                editText.text = AppConfig.webDavDir?.toEditable()
-            }
-        }
-        findPreference<EditTextPreference>(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<io.legado.app.lib.prefs.Preference>("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<AppLogDialog>()
-        }
-        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<Preference>(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()
-    }
-
-}

+ 0 - 46
app/src/main/java/io/legado/app/ui/config/ConfigActivity.kt

@@ -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<ActivityConfigBinding, ConfigViewModel>() {
-
-    override val binding by viewBinding(ActivityConfigBinding::inflate)
-    override val viewModel by viewModels<ConfigViewModel>()
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        setSupportActionBar(binding.topBar)
-        when (val configTag = intent.getStringExtra("configTag")) {
-            ConfigTag.OTHER_CONFIG -> replaceFragment<OtherConfigFragment>(configTag)
-            ConfigTag.THEME_CONFIG -> replaceFragment<ThemeConfigFragment>(configTag)
-            ConfigTag.BACKUP_CONFIG -> replaceFragment<BackupConfigFragment>(configTag)
-            ConfigTag.COVER_CONFIG -> replaceFragment<CoverConfigFragment>(configTag)
-            ConfigTag.READ_CONFIG -> replaceFragment<ReadConfigFragment>(configTag)
-            else -> finish()
-        }
-    }
-
-    override fun setTitle(resId: Int) {
-        super.setTitle(resId)
-        binding.topBar.setTitle(resId)
-        binding.topBar.setNavigationOnClickListener {
-            onBackPressedDispatcher.onBackPressed()
-        }
-    }
-
-    inline fun <reified T : Fragment> 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()
-    }
-}

+ 78 - 0
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
+                )
+            }
+        }
+    }
+}

+ 0 - 51
app/src/main/java/io/legado/app/ui/config/ConfigViewModel.kt

@@ -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)
-        }
-    }
-
-}

+ 0 - 168
app/src/main/java/io/legado/app/ui/config/CoverConfigFragment.kt

@@ -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<SwitchPreference>(PreferKey.coverShowAuthor)
-            ?.isEnabled = getPrefBoolean(PreferKey.coverShowName)
-        findPreference<SwitchPreference>(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<SwitchPreference>(PreferKey.coverShowAuthor)
-                    ?.isEnabled = getPrefBoolean(key)
-                BookCover.upDefaultCover()
-            }
-            PreferKey.coverShowNameN -> {
-                findPreference<SwitchPreference>(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<Preference>(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)
-            }
-        }
-    }
-
-}

+ 0 - 401
app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt

@@ -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<ConfigViewModel>()
-    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<DirectLinkUploadConfig>()
-            PreferKey.checkSource -> showDialogFragment<CheckSourceConfig>()
-            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<TestConfigActivity>()
-            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<Preference>(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()
-        }
-    }
-
-}

+ 0 - 175
app/src/main/java/io/legado/app/ui/config/ReadConfigFragment.kt

@@ -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<Preference>("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<Preference>(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)
-        }
-    }
-}

+ 0 - 153
app/src/main/java/io/legado/app/ui/config/TestConfigActivity.kt

@@ -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<ConfigNavRoute> {
-                ConfigNavScreen(
-                    onBackClick = { finish() },
-                    onNavigateToOther = { navController.navigate(OtherConfigRoute) },
-                    onNavigateToRead = { navController.navigate(ReadConfigRoute) },
-                    onNavigateToCover = { navController.navigate(CoverConfigRoute) },
-                    onNavigateToTheme = { navController.navigate(ThemeConfigRoute) },
-                    onNavigateToBackup = { navController.navigate(BackupConfigRoute) }
-                )
-            }
-
-            composable<OtherConfigRoute> {
-                OtherConfigScreen(onBackClick = { navController.popBackStack() })
-            }
-
-            composable<ReadConfigRoute> {
-                ReadConfigScreen(onBackClick = { navController.popBackStack() })
-            }
-
-            composable<CoverConfigRoute> {
-                CoverConfigScreen(onBackClick = { navController.popBackStack() })
-            }
-
-            composable<ThemeConfigRoute> {
-                ThemeConfigScreen(
-                    onBackClick = { navController.popBackStack() }
-                )
-            }
-
-            composable<BackupConfigRoute> {
-                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
-                    )
-                }
-            }
-        }
-    }
-
-}

+ 0 - 479
app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt

@@ -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<ColorPreference>(PreferKey.cBackground)?.let {
-            it.onSaveColor = { color ->
-                if (!ColorUtils.isColorLight(color)) {
-                    toastOnUi(R.string.day_background_too_dark)
-                    true
-                } else {
-                    false
-                }
-            }
-        }
-
-        findPreference<ColorPreference>(PreferKey.cNBackground)?.let {
-            it.onSaveColor = { color ->
-                if (ColorUtils.isColorLight(color)) {
-                    toastOnUi(R.string.night_background_too_light)
-                    true
-                } else {
-                    false
-                }
-            }
-        }
-
-        findPreference<ThemeModePreference>(PreferKey.themeMode)?.let {
-            it.setOnPreferenceChangeListener { _, _ ->
-                true
-            }
-        }
-
-        val themePref = findPreference<ThemeCardPreference>(PreferKey.themePref)
-        val colorImage = findPreference<ImagePreviewPreference>(PreferKey.colorImage)
-        val colorPrimary = findPreference<ColorPreference>("colorPrimary")
-        val customMode = findPreference<NameListPreference>("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<ConfigActivity> {
-                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<String>()
-
-            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<Preference>(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)
-            }
-        }
-    }
-
-}

+ 0 - 129
app/src/main/java/io/legado/app/ui/config/ThemeListDialog.kt

@@ -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<OldThemeConfig.Config, ItemThemeConfigBinding>(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<Any>
-        ) {
-            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)
-                }
-            }
-        }
-
-    }
-}

+ 9 - 0
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<String> {
+        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()

+ 59 - 0
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<String?>(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)

+ 184 - 0
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<Int?>(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))
+                }
+            }
+        )
+    }
+}

+ 264 - 1
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<MainViewModel>()
+    private val routeEvents = MutableSharedFlow<MainRoute>(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<MainRouteHome> {
+                    MainScreen(
+                        useRail = useRail,
+                        onOpenSettings = {
+                            navigateToRoute(backStack, MainRouteSettings)
+                        }
+                    )
+                }
+
+                entry<MainRouteSettings> {
+                    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<MainRouteSettingsOther> {
+                    OtherConfigScreen(onBackClick = { navigateBack(backStack) })
+                }
+
+                entry<MainRouteSettingsRead> {
+                    ReadConfigScreen(onBackClick = { navigateBack(backStack) })
+                }
+
+                entry<MainRouteSettingsCover> {
+                    CoverConfigScreen(onBackClick = { navigateBack(backStack) })
+                }
+
+                entry<MainRouteSettingsTheme> {
+                    ThemeConfigScreen(onBackClick = { navigateBack(backStack) })
+                }
+
+                entry<MainRouteSettingsBackup> {
+                    BackupConfigScreen(onBackClick = { navigateBack(backStack) })
+                }
+            }
+        )
+    }
+
+    private fun navigateToRoute(backStack: MutableList<NavKey>, 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<NavKey>) {
+        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()

+ 3 - 1
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)
                             }

+ 23 - 70
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
-                            )
-                        )
-                    }
-                )
+                title = "规则"
+            ) {
                 SettingItem(
-                    title = stringResource(R.string.other_setting),
-                    description = stringResource(R.string.other_setting_s),
-                    imageVector = Icons.Default.Settings,
+                    title = stringResource(R.string.book_source_manage),
+                    description = stringResource(R.string.book_source_manage_desc),
+                    imageVector = Icons.Default.Source,
                     onClick = {
                         onNavigate(
-                            PrefClickEvent.StartActivity(
-                                ConfigActivity::class.java,
-                                ConfigTag.OTHER_CONFIG
-                            )
+                            PrefClickEvent.StartActivity(BookSourceActivity::class.java)
                         )
                     }
                 )
-            }
-            SplicedColumnGroup(
-                modifier = Modifier.padding(horizontal = 16.dp),
-                title = "规则"
-            ) {
                 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,