[优化] 导航系统换成Navigation3,老旧设置页替换为新的Compose界面
This commit is contained in:
@@ -38,25 +38,11 @@ Design 3 风格重构版本。
|
|||||||
|
|
||||||
## 🛠️ 核心功能
|
## 🛠️ 核心功能
|
||||||
|
|
||||||
1. **自定义书源:** 强大的爬虫引擎,规则简单易懂,支持自定义抓取网页数据。
|
1. **多格式支持:** 支持本地 TXT、EPUB 阅读,智能扫描本地文件。
|
||||||
2. **多格式支持:** 支持本地 TXT、EPUB 阅读,智能扫描本地文件。
|
2. **高度自定义:** 切换字体、背景、行距、段距、加粗、简繁转换等。
|
||||||
3. **高度自定义:** 切换字体、背景、行距、段距、加粗、简繁转换等。
|
3. **净化替换:** 强力去除广告,替换正文内容。
|
||||||
4. **订阅内容:** 不止是书,可以订阅任何你想看的内容。
|
4. **翻页模式:** 覆盖、仿真、滑动、滚动等多种模式随心切换。
|
||||||
5. **净化替换:** 强力去除广告,替换正文内容。
|
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)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -78,6 +64,5 @@ Design 3 风格重构版本。
|
|||||||
|
|
||||||
## ⚠️ 免责声明
|
## ⚠️ 免责声明
|
||||||
|
|
||||||
本项目为 **Legado(阅读)** 的非官方分支,仅用于学习、交流与个人使用。
|
|
||||||
项目内容不代表原作者立场,与原项目作者不存在任何隶属或授权关系。
|
项目内容不代表原作者立场,与原项目作者不存在任何隶属或授权关系。
|
||||||
|
|
||||||
|
|||||||
@@ -211,15 +211,6 @@
|
|||||||
android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
|
android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
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
|
<activity
|
||||||
android:name=".ui.book.search.SearchActivity"
|
android:name=".ui.book.search.SearchActivity"
|
||||||
@@ -595,4 +586,4 @@
|
|||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ object PreferKey {
|
|||||||
const val bookshelfLayoutModePortrait = "bookshelfLayoutPortrait"
|
const val bookshelfLayoutModePortrait = "bookshelfLayoutPortrait"
|
||||||
const val bookshelfLayoutModeLandscape = "bookshelf_layout_landscape"
|
const val bookshelfLayoutModeLandscape = "bookshelf_layout_landscape"
|
||||||
const val bookshelfLayoutCompact = "bookshelfLayoutCompact"
|
const val bookshelfLayoutCompact = "bookshelfLayoutCompact"
|
||||||
|
const val bookshelfShowDivider = "bookshelfShowDivider"
|
||||||
const val bookshelfGridLayout = "bookshelfGridLayout"
|
const val bookshelfGridLayout = "bookshelfGridLayout"
|
||||||
const val bookshelfSort = "bookshelfSort"
|
const val bookshelfSort = "bookshelfSort"
|
||||||
const val bookExportFileName = "bookExportFileName"
|
const val bookExportFileName = "bookExportFileName"
|
||||||
|
|||||||
@@ -168,19 +168,10 @@ object OldThemeConfig {
|
|||||||
fun applyConfig(context: Context, config: Config) {
|
fun applyConfig(context: Context, config: Config) {
|
||||||
try {
|
try {
|
||||||
val primary = config.primaryColor.toColorInt()
|
val primary = config.primaryColor.toColorInt()
|
||||||
val accent = config.accentColor.toColorInt()
|
|
||||||
val background = config.backgroundColor.toColorInt()
|
|
||||||
val bBackground = config.bottomBackground.toColorInt()
|
|
||||||
if (config.isNightTheme) {
|
if (config.isNightTheme) {
|
||||||
context.putPrefInt(PreferKey.cNPrimary, primary)
|
context.putPrefInt(PreferKey.cNPrimary, primary)
|
||||||
context.putPrefInt(PreferKey.cNAccent, accent)
|
|
||||||
context.putPrefInt(PreferKey.cNBackground, background)
|
|
||||||
context.putPrefInt(PreferKey.cNBBackground, bBackground)
|
|
||||||
} else {
|
} else {
|
||||||
context.putPrefInt(PreferKey.cPrimary, primary)
|
context.putPrefInt(PreferKey.cPrimary, primary)
|
||||||
context.putPrefInt(PreferKey.cAccent, accent)
|
|
||||||
context.putPrefInt(PreferKey.cBackground, background)
|
|
||||||
context.putPrefInt(PreferKey.cBBackground, bBackground)
|
|
||||||
}
|
}
|
||||||
AppConfig.isNightTheme = config.isNightTheme
|
AppConfig.isNightTheme = config.isNightTheme
|
||||||
applyDayNight(context)
|
applyDayNight(context)
|
||||||
@@ -332,4 +323,4 @@ object OldThemeConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,6 +11,12 @@ import splitties.init.appCtx
|
|||||||
|
|
||||||
class BackupConfigViewModel : ViewModel() {
|
class BackupConfigViewModel : ViewModel() {
|
||||||
|
|
||||||
|
private suspend fun syncWebDavConfig() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
AppWebDav.upConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setWebDavAccount(account: String, password: String) {
|
fun setWebDavAccount(account: String, password: String) {
|
||||||
BackupConfig.webDavAccount = account
|
BackupConfig.webDavAccount = account
|
||||||
BackupConfig.webDavPassword = password
|
BackupConfig.webDavPassword = password
|
||||||
@@ -25,6 +31,7 @@ class BackupConfigViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun testWebDav(): Boolean {
|
suspend fun testWebDav(): Boolean {
|
||||||
|
syncWebDavConfig()
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
AppWebDav.testWebDav()
|
AppWebDav.testWebDav()
|
||||||
@@ -51,6 +58,7 @@ class BackupConfigViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getBackupNames(): List<String> {
|
suspend fun getBackupNames(): List<String> {
|
||||||
|
syncWebDavConfig()
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
AppWebDav.getBackupNames()
|
AppWebDav.getBackupNames()
|
||||||
}
|
}
|
||||||
@@ -59,6 +67,7 @@ class BackupConfigViewModel : ViewModel() {
|
|||||||
fun restoreWebDav(name: String, onSuccess: () -> Unit, onError: (String) -> Unit) {
|
fun restoreWebDav(name: String, onSuccess: () -> Unit, onError: (String) -> Unit) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
|
AppWebDav.upConfig()
|
||||||
AppWebDav.restoreWebDav(name)
|
AppWebDav.restoreWebDav(name)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
onSuccess()
|
onSuccess()
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.ToggleButton
|
import androidx.compose.material3.ToggleButton
|
||||||
@@ -108,6 +109,9 @@ fun ThemeConfigScreen(
|
|||||||
var showRestartDialog by remember { mutableStateOf(false) }
|
var showRestartDialog by remember { mutableStateOf(false) }
|
||||||
var showColorPicker by remember { mutableStateOf(false) }
|
var showColorPicker by remember { mutableStateOf(false) }
|
||||||
var showLauncherIconPicker 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 fontScaleValue = remember { mutableFloatStateOf(ThemeConfig.fontScale.toFloat()) }
|
||||||
val primaryColorValue = remember { mutableIntStateOf(ThemeConfig.cPrimary) }
|
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)) {
|
SplicedColumnGroup(title = stringResource(R.string.compose_related)) {
|
||||||
SwitchSettingItem(
|
SwitchSettingItem(
|
||||||
title = stringResource(R.string.use_flexible_top_bar),
|
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)
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,27 @@
|
|||||||
package io.legado.app.ui.main
|
package io.legado.app.ui.main
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.format.DateUtils
|
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.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.lifecycle.lifecycleScope
|
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.BuildConfig
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseComposeActivity
|
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.CrashLogsDialog
|
||||||
import io.legado.app.ui.about.UpdateDialog
|
import io.legado.app.ui.about.UpdateDialog
|
||||||
import io.legado.app.ui.book.read.ReadBookActivity
|
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.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.welcome.WelcomeActivity
|
||||||
import io.legado.app.ui.widget.dialog.TextDialog
|
import io.legado.app.ui.widget.dialog.TextDialog
|
||||||
import io.legado.app.utils.getPrefBoolean
|
import io.legado.app.utils.getPrefBoolean
|
||||||
import io.legado.app.utils.showDialogFragment
|
import io.legado.app.utils.showDialogFragment
|
||||||
import io.legado.app.utils.startActivity
|
import io.legado.app.utils.startActivity
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
@@ -41,7 +66,60 @@ import kotlin.coroutines.suspendCoroutine
|
|||||||
*/
|
*/
|
||||||
open class MainActivity : BaseComposeActivity() {
|
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 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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
installSplashScreen()
|
installSplashScreen()
|
||||||
@@ -70,6 +148,12 @@ open class MainActivity : BaseComposeActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
setIntent(intent)
|
||||||
|
routeEvents.tryEmit(resolveStartRoute(intent))
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val orientation = resources.configuration.orientation
|
val orientation = resources.configuration.orientation
|
||||||
@@ -84,7 +168,169 @@ open class MainActivity : BaseComposeActivity() {
|
|||||||
else -> false
|
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 {
|
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()
|
class LauncherW : MainActivity()
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ import org.koin.androidx.compose.koinViewModel
|
|||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(
|
fun MainScreen(
|
||||||
viewModel: MainViewModel = koinViewModel(),
|
viewModel: MainViewModel = koinViewModel(),
|
||||||
useRail: Boolean
|
useRail: Boolean,
|
||||||
|
onOpenSettings: () -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val uiState by viewModel.state.collectAsStateWithLifecycle()
|
val uiState by viewModel.state.collectAsStateWithLifecycle()
|
||||||
@@ -362,6 +363,7 @@ fun MainScreen(
|
|||||||
MainDestination.Rss -> RssScreen()
|
MainDestination.Rss -> RssScreen()
|
||||||
MainDestination.My -> MyScreen(
|
MainDestination.My -> MyScreen(
|
||||||
viewModel = koinViewModel(),
|
viewModel = koinViewModel(),
|
||||||
|
onOpenSettings = onOpenSettings,
|
||||||
onNavigate = { event ->
|
onNavigate = { event ->
|
||||||
viewModel.onPrefClickEvent(context, event)
|
viewModel.onPrefClickEvent(context, event)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.HelpOutline
|
||||||
import androidx.compose.material.icons.automirrored.filled.LibraryBooks
|
import androidx.compose.material.icons.automirrored.filled.LibraryBooks
|
||||||
import androidx.compose.material.icons.automirrored.filled.Rule
|
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.Bookmark
|
||||||
import androidx.compose.material.icons.filled.FindReplace
|
import androidx.compose.material.icons.filled.FindReplace
|
||||||
import androidx.compose.material.icons.filled.Folder
|
import androidx.compose.material.icons.filled.Folder
|
||||||
import androidx.compose.material.icons.filled.History
|
import androidx.compose.material.icons.filled.History
|
||||||
import androidx.compose.material.icons.filled.Info
|
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.Settings
|
||||||
import androidx.compose.material.icons.filled.Source
|
import androidx.compose.material.icons.filled.Source
|
||||||
import androidx.compose.material.icons.filled.Web
|
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.readRecord.ReadRecordActivity
|
||||||
import io.legado.app.ui.book.source.manage.BookSourceActivity
|
import io.legado.app.ui.book.source.manage.BookSourceActivity
|
||||||
import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
|
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.dict.rule.DictRuleActivity
|
||||||
import io.legado.app.ui.file.FileManageActivity
|
import io.legado.app.ui.file.FileManageActivity
|
||||||
import io.legado.app.ui.replace.ReplaceRuleActivity
|
import io.legado.app.ui.replace.ReplaceRuleActivity
|
||||||
@@ -61,6 +57,7 @@ import io.legado.app.ui.widget.components.settingItem.SettingItem
|
|||||||
@Composable
|
@Composable
|
||||||
fun MyScreen(
|
fun MyScreen(
|
||||||
viewModel: MyViewModel,
|
viewModel: MyViewModel,
|
||||||
|
onOpenSettings: () -> Unit,
|
||||||
onNavigate: (PrefClickEvent) -> Unit
|
onNavigate: (PrefClickEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -91,27 +88,20 @@ fun MyScreen(
|
|||||||
) { padding ->
|
) { padding ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(
|
|
||||||
top = padding.calculateTopPadding()
|
|
||||||
)
|
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
//TODO:暂时这样解决,改天加模糊
|
//TODO:暂时这样解决,改天加模糊
|
||||||
) {
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(
|
||||||
|
top = padding.calculateTopPadding()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
SplicedColumnGroup(
|
SplicedColumnGroup(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
title = ""
|
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(
|
SettingItem(
|
||||||
title = stringResource(R.string.web_service),
|
title = stringResource(R.string.web_service),
|
||||||
description = if (uiState.isWebServiceRun)
|
description = if (uiState.isWebServiceRun)
|
||||||
@@ -149,66 +139,21 @@ fun MyScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SplicedColumnGroup(
|
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
|
||||||
title = stringResource(R.string.setting)) {
|
|
||||||
SettingItem(
|
|
||||||
title = stringResource(R.string.backup_restore),
|
|
||||||
description = stringResource(R.string.web_dav_set_import_old),
|
|
||||||
imageVector = Icons.Default.Backup,
|
|
||||||
onClick = {
|
|
||||||
onNavigate(
|
|
||||||
PrefClickEvent.StartActivity(
|
|
||||||
ConfigActivity::class.java,
|
|
||||||
ConfigTag.BACKUP_CONFIG
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
SettingItem(
|
|
||||||
title = stringResource(R.string.theme_setting),
|
|
||||||
description = stringResource(R.string.theme_setting_s),
|
|
||||||
imageVector = Icons.Default.Palette,
|
|
||||||
onClick = {
|
|
||||||
onNavigate(
|
|
||||||
PrefClickEvent.StartActivity(
|
|
||||||
ConfigActivity::class.java,
|
|
||||||
ConfigTag.THEME_CONFIG
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
SettingItem(
|
|
||||||
title = stringResource(R.string.read),
|
|
||||||
description = stringResource(R.string.global_read_setting),
|
|
||||||
imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
|
|
||||||
onClick = {
|
|
||||||
onNavigate(
|
|
||||||
PrefClickEvent.StartActivity(
|
|
||||||
ConfigActivity::class.java,
|
|
||||||
ConfigTag.READ_CONFIG
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
SettingItem(
|
|
||||||
title = stringResource(R.string.other_setting),
|
|
||||||
description = stringResource(R.string.other_setting_s),
|
|
||||||
imageVector = Icons.Default.Settings,
|
|
||||||
onClick = {
|
|
||||||
onNavigate(
|
|
||||||
PrefClickEvent.StartActivity(
|
|
||||||
ConfigActivity::class.java,
|
|
||||||
ConfigTag.OTHER_CONFIG
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
SplicedColumnGroup(
|
SplicedColumnGroup(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
title = "规则"
|
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(
|
SettingItem(
|
||||||
title = stringResource(R.string.replace_purify),
|
title = stringResource(R.string.replace_purify),
|
||||||
imageVector = Icons.Default.FindReplace,
|
imageVector = Icons.Default.FindReplace,
|
||||||
@@ -229,7 +174,7 @@ fun MyScreen(
|
|||||||
)
|
)
|
||||||
SettingItem(
|
SettingItem(
|
||||||
title = stringResource(R.string.dict_rule),
|
title = stringResource(R.string.dict_rule),
|
||||||
imageVector = Icons.AutoMirrored.Filled.Rule,
|
imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
|
||||||
onClick = {
|
onClick = {
|
||||||
onNavigate(
|
onNavigate(
|
||||||
PrefClickEvent.StartActivity(DictRuleActivity::class.java)
|
PrefClickEvent.StartActivity(DictRuleActivity::class.java)
|
||||||
@@ -237,9 +182,17 @@ fun MyScreen(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SplicedColumnGroup(
|
SplicedColumnGroup(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
title = stringResource(R.string.other)) {
|
title = stringResource(R.string.other)) {
|
||||||
|
SettingItem(
|
||||||
|
title = stringResource(R.string.setting),
|
||||||
|
imageVector = Icons.Default.Settings,
|
||||||
|
onClick = {
|
||||||
|
onOpenSettings()
|
||||||
|
}
|
||||||
|
)
|
||||||
SettingItem(
|
SettingItem(
|
||||||
title = stringResource(R.string.bookmark),
|
title = stringResource(R.string.bookmark),
|
||||||
imageVector = Icons.Default.Bookmark,
|
imageVector = Icons.Default.Bookmark,
|
||||||
|
|||||||
Reference in New Issue
Block a user