[优化] 导航系统换成Navigation3,老旧设置页替换为新的Compose界面
This commit is contained in:
@@ -38,25 +38,11 @@ Design 3 风格重构版本。
|
||||
|
||||
## 🛠️ 核心功能
|
||||
|
||||
1. **自定义书源:** 强大的爬虫引擎,规则简单易懂,支持自定义抓取网页数据。
|
||||
2. **多格式支持:** 支持本地 TXT、EPUB 阅读,智能扫描本地文件。
|
||||
3. **高度自定义:** 切换字体、背景、行距、段距、加粗、简繁转换等。
|
||||
4. **订阅内容:** 不止是书,可以订阅任何你想看的内容。
|
||||
5. **净化替换:** 强力去除广告,替换正文内容。
|
||||
6. **翻页模式:** 覆盖、仿真、滑动、滚动等多种模式随心切换。
|
||||
7. **完全开源:** 无广告,持续迭代优化。
|
||||
|
||||
---
|
||||
|
||||
## 🤝 社区与支持
|
||||
|
||||
由于本项目的开发者比较忙,暂时没有独立社区。
|
||||
|
||||
以下为原项目的社区:
|
||||
|
||||
* **官方 Wiki:** [书源规则教程](https://mgz0227.github.io/The-tutorial-of-Legado/)
|
||||
* **交流社区:** [Telegram 频道](https://t.me/legado_channels) | [Discord](https://discord.gg/VtUfRyzRXn) | [语雀 Wiki](https://www.yuque.com/legado/wiki/community)
|
||||
* **相关文档:** [免责声明](https://gedoor.github.io/Disclaimer)
|
||||
1. **多格式支持:** 支持本地 TXT、EPUB 阅读,智能扫描本地文件。
|
||||
2. **高度自定义:** 切换字体、背景、行距、段距、加粗、简繁转换等。
|
||||
3. **净化替换:** 强力去除广告,替换正文内容。
|
||||
4. **翻页模式:** 覆盖、仿真、滑动、滚动等多种模式随心切换。
|
||||
5. **完全开源:** 无广告,持续迭代优化。
|
||||
|
||||
---
|
||||
|
||||
@@ -78,6 +64,5 @@ Design 3 风格重构版本。
|
||||
|
||||
## ⚠️ 免责声明
|
||||
|
||||
本项目为 **Legado(阅读)** 的非官方分支,仅用于学习、交流与个人使用。
|
||||
项目内容不代表原作者立场,与原项目作者不存在任何隶属或授权关系。
|
||||
|
||||
|
||||
@@ -211,15 +211,6 @@
|
||||
android:configChanges="locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||
<!-- 配置界面 -->
|
||||
<activity
|
||||
android:name=".ui.config.ConfigActivity"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:launchMode="standard" />
|
||||
<activity
|
||||
android:name=".ui.config.TestConfigActivity"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:launchMode="standard" />
|
||||
<!-- 搜索界面 -->
|
||||
<activity
|
||||
android:name=".ui.book.search.SearchActivity"
|
||||
@@ -595,4 +586,4 @@
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -52,6 +52,7 @@ object PreferKey {
|
||||
const val bookshelfLayoutModePortrait = "bookshelfLayoutPortrait"
|
||||
const val bookshelfLayoutModeLandscape = "bookshelf_layout_landscape"
|
||||
const val bookshelfLayoutCompact = "bookshelfLayoutCompact"
|
||||
const val bookshelfShowDivider = "bookshelfShowDivider"
|
||||
const val bookshelfGridLayout = "bookshelfGridLayout"
|
||||
const val bookshelfSort = "bookshelfSort"
|
||||
const val bookExportFileName = "bookExportFileName"
|
||||
|
||||
@@ -168,19 +168,10 @@ object OldThemeConfig {
|
||||
fun applyConfig(context: Context, config: Config) {
|
||||
try {
|
||||
val primary = config.primaryColor.toColorInt()
|
||||
val accent = config.accentColor.toColorInt()
|
||||
val background = config.backgroundColor.toColorInt()
|
||||
val bBackground = config.bottomBackground.toColorInt()
|
||||
if (config.isNightTheme) {
|
||||
context.putPrefInt(PreferKey.cNPrimary, primary)
|
||||
context.putPrefInt(PreferKey.cNAccent, accent)
|
||||
context.putPrefInt(PreferKey.cNBackground, background)
|
||||
context.putPrefInt(PreferKey.cNBBackground, bBackground)
|
||||
} else {
|
||||
context.putPrefInt(PreferKey.cPrimary, primary)
|
||||
context.putPrefInt(PreferKey.cAccent, accent)
|
||||
context.putPrefInt(PreferKey.cBackground, background)
|
||||
context.putPrefInt(PreferKey.cBBackground, bBackground)
|
||||
}
|
||||
AppConfig.isNightTheme = config.isNightTheme
|
||||
applyDayNight(context)
|
||||
@@ -332,4 +323,4 @@ object OldThemeConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
private suspend fun syncWebDavConfig() {
|
||||
withContext(Dispatchers.IO) {
|
||||
AppWebDav.upConfig()
|
||||
}
|
||||
}
|
||||
|
||||
fun setWebDavAccount(account: String, password: String) {
|
||||
BackupConfig.webDavAccount = account
|
||||
BackupConfig.webDavPassword = password
|
||||
@@ -25,6 +31,7 @@ class BackupConfigViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
suspend fun testWebDav(): Boolean {
|
||||
syncWebDavConfig()
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
AppWebDav.testWebDav()
|
||||
@@ -51,6 +58,7 @@ class BackupConfigViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
suspend fun getBackupNames(): List<String> {
|
||||
syncWebDavConfig()
|
||||
return withContext(Dispatchers.IO) {
|
||||
AppWebDav.getBackupNames()
|
||||
}
|
||||
@@ -59,6 +67,7 @@ class BackupConfigViewModel : ViewModel() {
|
||||
fun restoreWebDav(name: String, onSuccess: () -> Unit, onError: (String) -> Unit) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
AppWebDav.upConfig()
|
||||
AppWebDav.restoreWebDav(name)
|
||||
withContext(Dispatchers.Main) {
|
||||
onSuccess()
|
||||
|
||||
@@ -42,6 +42,7 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.ToggleButton
|
||||
@@ -108,6 +109,9 @@ fun ThemeConfigScreen(
|
||||
var showRestartDialog by remember { mutableStateOf(false) }
|
||||
var showColorPicker by remember { mutableStateOf(false) }
|
||||
var showLauncherIconPicker by remember { mutableStateOf(false) }
|
||||
var showThemeListDialog by remember { mutableStateOf(false) }
|
||||
var saveThemeKey by remember { mutableStateOf<String?>(null) }
|
||||
var themeName by remember { mutableStateOf("") }
|
||||
|
||||
val fontScaleValue = remember { mutableFloatStateOf(ThemeConfig.fontScale.toFloat()) }
|
||||
val primaryColorValue = remember { mutableIntStateOf(ThemeConfig.cPrimary) }
|
||||
@@ -339,6 +343,22 @@ fun ThemeConfigScreen(
|
||||
)
|
||||
}
|
||||
|
||||
SplicedColumnGroup(title = stringResource(R.string.share)) {
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.theme_list),
|
||||
description = stringResource(R.string.theme_list_summary),
|
||||
onClick = { showThemeListDialog = true }
|
||||
)
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.save_theme_config),
|
||||
description = stringResource(R.string.save_day_theme_summary),
|
||||
onClick = {
|
||||
saveThemeKey = "saveDayTheme"
|
||||
themeName = ""
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
SplicedColumnGroup(title = stringResource(R.string.compose_related)) {
|
||||
SwitchSettingItem(
|
||||
title = stringResource(R.string.use_flexible_top_bar),
|
||||
@@ -483,6 +503,45 @@ fun ThemeConfigScreen(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (showThemeListDialog) {
|
||||
ThemeListDialog(
|
||||
onDismissRequest = { showThemeListDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
saveThemeKey?.let { key ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { saveThemeKey = null },
|
||||
title = { Text(stringResource(R.string.theme_name)) },
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
value = themeName,
|
||||
onValueChange = { themeName = it },
|
||||
label = { Text("name") },
|
||||
singleLine = true
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
when (key) {
|
||||
"saveDayTheme" -> OldThemeConfig.saveDayTheme(context, themeName)
|
||||
"saveNightTheme" -> OldThemeConfig.saveNightTheme(context, themeName)
|
||||
}
|
||||
saveThemeKey = null
|
||||
}
|
||||
) {
|
||||
Text(stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { saveThemeKey = null }) {
|
||||
Text(stringResource(android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
|
||||
@@ -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
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import androidx.navigation3.runtime.entryProvider
|
||||
import androidx.navigation3.runtime.rememberNavBackStack
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import io.legado.app.BuildConfig
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseComposeActivity
|
||||
@@ -23,15 +39,24 @@ import io.legado.app.service.WebService
|
||||
import io.legado.app.ui.about.CrashLogsDialog
|
||||
import io.legado.app.ui.about.UpdateDialog
|
||||
import io.legado.app.ui.book.read.ReadBookActivity
|
||||
import io.legado.app.ui.config.ConfigNavScreen
|
||||
import io.legado.app.ui.config.ConfigTag
|
||||
import io.legado.app.ui.config.backupConfig.BackupConfigScreen
|
||||
import io.legado.app.ui.config.coverConfig.CoverConfigScreen
|
||||
import io.legado.app.ui.config.mainConfig.MainConfig
|
||||
import io.legado.app.ui.config.otherConfig.OtherConfigScreen
|
||||
import io.legado.app.ui.config.readConfig.ReadConfigScreen
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfigScreen
|
||||
import io.legado.app.ui.welcome.WelcomeActivity
|
||||
import io.legado.app.ui.widget.dialog.TextDialog
|
||||
import io.legado.app.utils.getPrefBoolean
|
||||
import io.legado.app.utils.showDialogFragment
|
||||
import io.legado.app.utils.startActivity
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
@@ -41,7 +66,60 @@ import kotlin.coroutines.suspendCoroutine
|
||||
*/
|
||||
open class MainActivity : BaseComposeActivity() {
|
||||
|
||||
companion object {
|
||||
const val EXTRA_START_ROUTE = "startRoute"
|
||||
private const val ROUTE_MAIN = "main"
|
||||
private const val ROUTE_SETTINGS = "settings"
|
||||
private const val ROUTE_SETTINGS_OTHER = "settings/other"
|
||||
private const val ROUTE_SETTINGS_READ = "settings/read"
|
||||
private const val ROUTE_SETTINGS_COVER = "settings/cover"
|
||||
private const val ROUTE_SETTINGS_THEME = "settings/theme"
|
||||
private const val ROUTE_SETTINGS_BACKUP = "settings/backup"
|
||||
|
||||
fun createIntent(context: Context, configTag: String? = null): Intent {
|
||||
return Intent(context, MainActivity::class.java).apply {
|
||||
putExtra(EXTRA_START_ROUTE, routeForConfigTag(configTag))
|
||||
}
|
||||
}
|
||||
|
||||
private fun routeForConfigTag(configTag: String?): String {
|
||||
return when (configTag) {
|
||||
ConfigTag.OTHER_CONFIG -> ROUTE_SETTINGS_OTHER
|
||||
ConfigTag.READ_CONFIG -> ROUTE_SETTINGS_READ
|
||||
ConfigTag.COVER_CONFIG -> ROUTE_SETTINGS_COVER
|
||||
ConfigTag.THEME_CONFIG -> ROUTE_SETTINGS_THEME
|
||||
ConfigTag.BACKUP_CONFIG -> ROUTE_SETTINGS_BACKUP
|
||||
else -> ROUTE_SETTINGS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val viewModel by viewModel<MainViewModel>()
|
||||
private val routeEvents = MutableSharedFlow<MainRoute>(extraBufferCapacity = 1)
|
||||
|
||||
@Serializable
|
||||
private sealed interface MainRoute : NavKey
|
||||
|
||||
@Serializable
|
||||
private data object MainRouteHome : MainRoute
|
||||
|
||||
@Serializable
|
||||
private data object MainRouteSettings : MainRoute
|
||||
|
||||
@Serializable
|
||||
private data object MainRouteSettingsOther : MainRoute
|
||||
|
||||
@Serializable
|
||||
private data object MainRouteSettingsRead : MainRoute
|
||||
|
||||
@Serializable
|
||||
private data object MainRouteSettingsCover : MainRoute
|
||||
|
||||
@Serializable
|
||||
private data object MainRouteSettingsTheme : MainRoute
|
||||
|
||||
@Serializable
|
||||
private data object MainRouteSettingsBackup : MainRoute
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
@@ -70,6 +148,12 @@ open class MainActivity : BaseComposeActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
routeEvents.tryEmit(resolveStartRoute(intent))
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val orientation = resources.configuration.orientation
|
||||
@@ -84,7 +168,169 @@ open class MainActivity : BaseComposeActivity() {
|
||||
else -> false
|
||||
}
|
||||
|
||||
MainScreen(useRail = useRail)
|
||||
val backStack = rememberNavBackStack(resolveStartRoute(intent))
|
||||
|
||||
LaunchedEffect(backStack) {
|
||||
routeEvents.collect { route ->
|
||||
navigateToRoute(backStack, route)
|
||||
}
|
||||
}
|
||||
|
||||
NavDisplay(
|
||||
backStack = backStack,
|
||||
transitionSpec = {
|
||||
(slideIntoContainer(
|
||||
towards = AnimatedContentTransitionScope.SlideDirection.Start,
|
||||
animationSpec = tween(
|
||||
durationMillis = 260,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
initialOffset = { fullWidth -> fullWidth / 10 }
|
||||
) + fadeIn(
|
||||
animationSpec = tween(
|
||||
durationMillis = 220,
|
||||
easing = LinearOutSlowInEasing
|
||||
)
|
||||
)) togetherWith ExitTransition.None
|
||||
},
|
||||
popTransitionSpec = {
|
||||
(slideIntoContainer(
|
||||
towards = AnimatedContentTransitionScope.SlideDirection.Start,
|
||||
animationSpec = tween(
|
||||
durationMillis = 240,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
initialOffset = { fullWidth -> -fullWidth / 8 }
|
||||
) + fadeIn(
|
||||
animationSpec = tween(
|
||||
durationMillis = 200,
|
||||
easing = LinearOutSlowInEasing
|
||||
)
|
||||
)) togetherWith
|
||||
(scaleOut(
|
||||
targetScale = 0.92f,
|
||||
animationSpec = tween(
|
||||
durationMillis = 220,
|
||||
easing = FastOutSlowInEasing
|
||||
)
|
||||
) + fadeOut(
|
||||
animationSpec = tween(durationMillis = 180)
|
||||
))
|
||||
},
|
||||
predictivePopTransitionSpec = { _ ->
|
||||
(slideIntoContainer(
|
||||
towards = AnimatedContentTransitionScope.SlideDirection.Start,
|
||||
animationSpec = tween(
|
||||
durationMillis = 240,
|
||||
easing = FastOutSlowInEasing
|
||||
),
|
||||
initialOffset = { fullWidth -> -fullWidth / 8 }
|
||||
) + fadeIn(
|
||||
animationSpec = tween(
|
||||
durationMillis = 200,
|
||||
easing = LinearOutSlowInEasing
|
||||
)
|
||||
)) togetherWith
|
||||
(scaleOut(
|
||||
targetScale = 0.92f,
|
||||
animationSpec = tween(
|
||||
durationMillis = 220,
|
||||
easing = FastOutSlowInEasing
|
||||
)
|
||||
) + fadeOut(
|
||||
animationSpec = tween(durationMillis = 180)
|
||||
))
|
||||
},
|
||||
onBack = {
|
||||
if (backStack.size > 1) {
|
||||
backStack.removeLastOrNull()
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
},
|
||||
entryProvider = entryProvider {
|
||||
entry<MainRouteHome> {
|
||||
MainScreen(
|
||||
useRail = useRail,
|
||||
onOpenSettings = {
|
||||
navigateToRoute(backStack, MainRouteSettings)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
entry<MainRouteSettings> {
|
||||
ConfigNavScreen(
|
||||
onBackClick = { navigateBack(backStack) },
|
||||
onNavigateToOther = { backStack.add(MainRouteSettingsOther) },
|
||||
onNavigateToRead = { backStack.add(MainRouteSettingsRead) },
|
||||
onNavigateToCover = { backStack.add(MainRouteSettingsCover) },
|
||||
onNavigateToTheme = { backStack.add(MainRouteSettingsTheme) },
|
||||
onNavigateToBackup = { backStack.add(MainRouteSettingsBackup) }
|
||||
)
|
||||
}
|
||||
|
||||
entry<MainRouteSettingsOther> {
|
||||
OtherConfigScreen(onBackClick = { navigateBack(backStack) })
|
||||
}
|
||||
|
||||
entry<MainRouteSettingsRead> {
|
||||
ReadConfigScreen(onBackClick = { navigateBack(backStack) })
|
||||
}
|
||||
|
||||
entry<MainRouteSettingsCover> {
|
||||
CoverConfigScreen(onBackClick = { navigateBack(backStack) })
|
||||
}
|
||||
|
||||
entry<MainRouteSettingsTheme> {
|
||||
ThemeConfigScreen(onBackClick = { navigateBack(backStack) })
|
||||
}
|
||||
|
||||
entry<MainRouteSettingsBackup> {
|
||||
BackupConfigScreen(onBackClick = { navigateBack(backStack) })
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun navigateToRoute(backStack: MutableList<NavKey>, route: MainRoute) {
|
||||
val currentRoute = backStack.lastOrNull()
|
||||
if (currentRoute == route) return
|
||||
|
||||
when (route) {
|
||||
MainRouteHome -> {
|
||||
backStack.clear()
|
||||
backStack.add(MainRouteHome)
|
||||
}
|
||||
|
||||
MainRouteSettings -> {
|
||||
if (currentRoute == MainRouteHome) {
|
||||
backStack.add(MainRouteSettings)
|
||||
} else {
|
||||
backStack.clear()
|
||||
backStack.add(MainRouteHome)
|
||||
backStack.add(MainRouteSettings)
|
||||
}
|
||||
}
|
||||
|
||||
MainRouteSettingsOther,
|
||||
MainRouteSettingsRead,
|
||||
MainRouteSettingsCover,
|
||||
MainRouteSettingsTheme,
|
||||
MainRouteSettingsBackup -> {
|
||||
backStack.clear()
|
||||
backStack.add(MainRouteHome)
|
||||
backStack.add(MainRouteSettings)
|
||||
backStack.add(route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateBack(backStack: MutableList<NavKey>) {
|
||||
if (backStack.size > 1) {
|
||||
backStack.removeLastOrNull()
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkStartupRoute(): Boolean {
|
||||
@@ -197,6 +443,23 @@ open class MainActivity : BaseComposeActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveStartRoute(intent: Intent?): MainRoute {
|
||||
return resolveStartRoute(intent?.getStringExtra(EXTRA_START_ROUTE))
|
||||
}
|
||||
|
||||
private fun resolveStartRoute(route: String?): MainRoute {
|
||||
return when (route) {
|
||||
"main" -> MainRouteHome
|
||||
"settings" -> MainRouteSettings
|
||||
"settings/other" -> MainRouteSettingsOther
|
||||
"settings/read" -> MainRouteSettingsRead
|
||||
"settings/cover" -> MainRouteSettingsCover
|
||||
"settings/theme" -> MainRouteSettingsTheme
|
||||
"settings/backup" -> MainRouteSettingsBackup
|
||||
else -> MainRouteHome
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LauncherW : MainActivity()
|
||||
|
||||
@@ -78,7 +78,8 @@ import org.koin.androidx.compose.koinViewModel
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
viewModel: MainViewModel = koinViewModel(),
|
||||
useRail: Boolean
|
||||
useRail: Boolean,
|
||||
onOpenSettings: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val uiState by viewModel.state.collectAsStateWithLifecycle()
|
||||
@@ -362,6 +363,7 @@ fun MainScreen(
|
||||
MainDestination.Rss -> RssScreen()
|
||||
MainDestination.My -> MyScreen(
|
||||
viewModel = koinViewModel(),
|
||||
onOpenSettings = onOpenSettings,
|
||||
onNavigate = { event ->
|
||||
viewModel.onPrefClickEvent(context, event)
|
||||
}
|
||||
|
||||
@@ -15,13 +15,11 @@ import androidx.compose.material.icons.automirrored.filled.ExitToApp
|
||||
import androidx.compose.material.icons.automirrored.filled.HelpOutline
|
||||
import androidx.compose.material.icons.automirrored.filled.LibraryBooks
|
||||
import androidx.compose.material.icons.automirrored.filled.Rule
|
||||
import androidx.compose.material.icons.filled.Backup
|
||||
import androidx.compose.material.icons.filled.Bookmark
|
||||
import androidx.compose.material.icons.filled.FindReplace
|
||||
import androidx.compose.material.icons.filled.Folder
|
||||
import androidx.compose.material.icons.filled.History
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.Palette
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Source
|
||||
import androidx.compose.material.icons.filled.Web
|
||||
@@ -45,8 +43,6 @@ import io.legado.app.ui.book.bookmark.AllBookmarkActivity
|
||||
import io.legado.app.ui.book.readRecord.ReadRecordActivity
|
||||
import io.legado.app.ui.book.source.manage.BookSourceActivity
|
||||
import io.legado.app.ui.book.toc.rule.TxtTocRuleActivity
|
||||
import io.legado.app.ui.config.ConfigActivity
|
||||
import io.legado.app.ui.config.ConfigTag
|
||||
import io.legado.app.ui.dict.rule.DictRuleActivity
|
||||
import io.legado.app.ui.file.FileManageActivity
|
||||
import io.legado.app.ui.replace.ReplaceRuleActivity
|
||||
@@ -61,6 +57,7 @@ import io.legado.app.ui.widget.components.settingItem.SettingItem
|
||||
@Composable
|
||||
fun MyScreen(
|
||||
viewModel: MyViewModel,
|
||||
onOpenSettings: () -> Unit,
|
||||
onNavigate: (PrefClickEvent) -> Unit
|
||||
) {
|
||||
|
||||
@@ -91,27 +88,20 @@ fun MyScreen(
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = padding.calculateTopPadding()
|
||||
)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
//TODO:暂时这样解决,改天加模糊
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = padding.calculateTopPadding()
|
||||
)
|
||||
)
|
||||
|
||||
SplicedColumnGroup(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
title = ""
|
||||
) {
|
||||
SettingItem(
|
||||
title = stringResource(R.string.book_source_manage),
|
||||
description = stringResource(R.string.book_source_manage_desc),
|
||||
imageVector = Icons.Default.Source,
|
||||
onClick = {
|
||||
onNavigate(
|
||||
PrefClickEvent.StartActivity(BookSourceActivity::class.java)
|
||||
)
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
title = stringResource(R.string.web_service),
|
||||
description = if (uiState.isWebServiceRun)
|
||||
@@ -149,66 +139,21 @@ fun MyScreen(
|
||||
)
|
||||
}
|
||||
|
||||
SplicedColumnGroup(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
title = stringResource(R.string.setting)) {
|
||||
SettingItem(
|
||||
title = stringResource(R.string.backup_restore),
|
||||
description = stringResource(R.string.web_dav_set_import_old),
|
||||
imageVector = Icons.Default.Backup,
|
||||
onClick = {
|
||||
onNavigate(
|
||||
PrefClickEvent.StartActivity(
|
||||
ConfigActivity::class.java,
|
||||
ConfigTag.BACKUP_CONFIG
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
title = stringResource(R.string.theme_setting),
|
||||
description = stringResource(R.string.theme_setting_s),
|
||||
imageVector = Icons.Default.Palette,
|
||||
onClick = {
|
||||
onNavigate(
|
||||
PrefClickEvent.StartActivity(
|
||||
ConfigActivity::class.java,
|
||||
ConfigTag.THEME_CONFIG
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
title = stringResource(R.string.read),
|
||||
description = stringResource(R.string.global_read_setting),
|
||||
imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
|
||||
onClick = {
|
||||
onNavigate(
|
||||
PrefClickEvent.StartActivity(
|
||||
ConfigActivity::class.java,
|
||||
ConfigTag.READ_CONFIG
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
title = stringResource(R.string.other_setting),
|
||||
description = stringResource(R.string.other_setting_s),
|
||||
imageVector = Icons.Default.Settings,
|
||||
onClick = {
|
||||
onNavigate(
|
||||
PrefClickEvent.StartActivity(
|
||||
ConfigActivity::class.java,
|
||||
ConfigTag.OTHER_CONFIG
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
SplicedColumnGroup(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
title = "规则"
|
||||
) {
|
||||
SettingItem(
|
||||
title = stringResource(R.string.book_source_manage),
|
||||
description = stringResource(R.string.book_source_manage_desc),
|
||||
imageVector = Icons.Default.Source,
|
||||
onClick = {
|
||||
onNavigate(
|
||||
PrefClickEvent.StartActivity(BookSourceActivity::class.java)
|
||||
)
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
title = stringResource(R.string.replace_purify),
|
||||
imageVector = Icons.Default.FindReplace,
|
||||
@@ -229,7 +174,7 @@ fun MyScreen(
|
||||
)
|
||||
SettingItem(
|
||||
title = stringResource(R.string.dict_rule),
|
||||
imageVector = Icons.AutoMirrored.Filled.Rule,
|
||||
imageVector = Icons.AutoMirrored.Filled.LibraryBooks,
|
||||
onClick = {
|
||||
onNavigate(
|
||||
PrefClickEvent.StartActivity(DictRuleActivity::class.java)
|
||||
@@ -237,9 +182,17 @@ fun MyScreen(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
SplicedColumnGroup(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
title = stringResource(R.string.other)) {
|
||||
SettingItem(
|
||||
title = stringResource(R.string.setting),
|
||||
imageVector = Icons.Default.Settings,
|
||||
onClick = {
|
||||
onOpenSettings()
|
||||
}
|
||||
)
|
||||
SettingItem(
|
||||
title = stringResource(R.string.bookmark),
|
||||
imageVector = Icons.Default.Bookmark,
|
||||
|
||||
Reference in New Issue
Block a user