主题导入导出

pull/363/head
gedoor 4 years ago
parent c0be0dba20
commit bc88696004
  1. 46
      app/src/main/assets/themeConfig.json
  2. 4
      app/src/main/assets/updateLog.md
  3. 7
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  4. 93
      app/src/main/java/io/legado/app/help/ThemeConfig.kt
  5. 4
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  6. 19
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  7. 41
      app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt
  8. 98
      app/src/main/java/io/legado/app/ui/config/ThemeListDialog.kt
  9. 42
      app/src/main/res/layout/item_theme_config.xml

@ -1,6 +1,6 @@
[ [
{ {
"configName": "典雅蓝", "themeName": "典雅蓝",
"isNightTheme": false, "isNightTheme": false,
"primaryColor": "#03A9F4", "primaryColor": "#03A9F4",
"accentColor": "#AD1457", "accentColor": "#AD1457",
@ -8,43 +8,19 @@
"bottomBackground": "#EEEEEE" "bottomBackground": "#EEEEEE"
}, },
{ {
"configName": "极简", "themeName": "黑白",
"isNightTheme": false, "isNightTheme": false,
"primaryColor": "#03A9F4", "primaryColor": "#303030",
"accentColor": "#AD1457", "accentColor": "#E0E0E0",
"backgroundColor": "#F5F5F5", "backgroundColor": "#424242",
"bottomBackground": "#EEEEEE" "bottomBackground": "#424242"
}, },
{ {
"configName": "黑", "themeName": "A屏黑",
"isNightTheme": false, "isNightTheme": false,
"primaryColor": "#03A9F4", "primaryColor": "#000000",
"accentColor": "#AD1457", "accentColor": "#FFFFFF",
"backgroundColor": "#F5F5F5", "backgroundColor": "#000000",
"bottomBackground": "#EEEEEE" "bottomBackground": "#000000"
},
{
"configName": "曜夜",
"isNightTheme": false,
"primaryColor": "#03A9F4",
"accentColor": "#AD1457",
"backgroundColor": "#F5F5F5",
"bottomBackground": "#EEEEEE"
},
{
"configName": "黑白",
"isNightTheme": false,
"primaryColor": "#03A9F4",
"accentColor": "#AD1457",
"backgroundColor": "#F5F5F5",
"bottomBackground": "#EEEEEE"
},
{
"configName": "A屏黑",
"isNightTheme": false,
"primaryColor": "#03A9F4",
"accentColor": "#AD1457",
"backgroundColor": "#F5F5F5",
"bottomBackground": "#EEEEEE"
} }
] ]

@ -3,6 +3,10 @@
* 关注合作公众号 **[小说拾遗]()** 获取好看的小说。 * 关注合作公众号 **[小说拾遗]()** 获取好看的小说。
- 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 - 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2020/09/08**
* 页眉页脚跟随背景
* 主题导入导出
**2020/09/07** **2020/09/07**
* 订阅源和替换规则添加滑动选择 * 订阅源和替换规则添加滑动选择
* 修复排版配置导入导出 * 修复排版配置导入导出

@ -20,11 +20,11 @@ import java.io.File
*/ */
@Keep @Keep
object ReadBookConfig { object ReadBookConfig {
const val readConfigFileName = "readConfig.json" const val configFileName = "readConfig.json"
private val configFilePath = FileUtils.getPath(App.INSTANCE.filesDir, readConfigFileName) val configFilePath = FileUtils.getPath(App.INSTANCE.filesDir, configFileName)
val configList: ArrayList<Config> = arrayListOf() val configList: ArrayList<Config> = arrayListOf()
private val defaultConfigs by lazy { private val defaultConfigs by lazy {
val json = String(App.INSTANCE.assets.open(readConfigFileName).readBytes()) val json = String(App.INSTANCE.assets.open(configFileName).readBytes())
GSON.fromJsonArray<Config>(json)!! GSON.fromJsonArray<Config>(json)!!
} }
var durConfig var durConfig
@ -95,6 +95,7 @@ object ReadBookConfig {
Coroutine.async { Coroutine.async {
synchronized(this) { synchronized(this) {
val json = GSON.toJson(configList) val json = GSON.toJson(configList)
FileUtils.deleteFile(configFilePath)
FileUtils.createFileIfNotExist(configFilePath).writeText(json) FileUtils.createFileIfNotExist(configFilePath).writeText(json)
} }
} }

@ -6,17 +6,62 @@ import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.utils.* import io.legado.app.utils.*
import java.io.File
object ThemeConfig { object ThemeConfig {
val configFileName = "themeConfigs.json" const val configFileName = "themeConfig.json"
private val configFilePath = FileUtils.getPath(App.INSTANCE.filesDir, configFileName) val configFilePath = FileUtils.getPath(App.INSTANCE.filesDir, configFileName)
val configList = arrayListOf<Config>()
private val defaultConfigs by lazy { private val defaultConfigs by lazy {
val json = String(App.INSTANCE.assets.open(configFileName).readBytes()) val json = String(App.INSTANCE.assets.open(configFileName).readBytes())
GSON.fromJsonArray<Config>(json)!! GSON.fromJsonArray<Config>(json)!!
} }
val configList = arrayListOf<Config>()
init {
upConfig()
}
fun upConfig() {
(getConfigs() ?: defaultConfigs).let {
configList.clear()
configList.addAll(it)
}
}
fun save() {
Coroutine.async {
synchronized(this) {
val json = GSON.toJson(configList)
FileUtils.deleteFile(configFilePath)
FileUtils.createFileIfNotExist(configFilePath).writeText(json)
}
}
}
fun addConfig(newConfig: Config) {
configList.forEachIndexed { index, config ->
if (newConfig.themeName == config.themeName) {
configList[index] = newConfig
return
}
}
configList.add(newConfig)
}
private fun getConfigs(): List<Config>? {
val configFile = File(configFilePath)
if (configFile.exists()) {
try {
val json = configFile.readText()
return GSON.fromJsonArray(json)
} catch (e: Exception) {
e.printStackTrace()
}
}
return null
}
fun applyConfig(context: Context, config: Config) { fun applyConfig(context: Context, config: Config) {
val primary = Color.parseColor(config.primaryColor) val primary = Color.parseColor(config.primaryColor)
@ -42,8 +87,48 @@ object ThemeConfig {
postEvent(EventBus.RECREATE, "") postEvent(EventBus.RECREATE, "")
} }
fun saveDayTheme(context: Context, name: String) {
val primary =
context.getPrefInt(PreferKey.cPrimary, context.getCompatColor(R.color.md_brown_500))
val accent =
context.getPrefInt(PreferKey.cAccent, context.getCompatColor(R.color.md_red_600))
val background =
context.getPrefInt(PreferKey.cBackground, context.getCompatColor(R.color.md_grey_100))
val bBackground =
context.getPrefInt(PreferKey.cBBackground, context.getCompatColor(R.color.md_grey_200))
val config = Config(
themeName = name,
isNightTheme = false,
primaryColor = "#${primary.hexString}",
accentColor = "#${accent.hexString}",
backgroundColor = "#${background.hexString}",
bottomBackground = "#${bBackground.hexString}"
)
addConfig(config)
}
fun saveNightTheme(context: Context, name: String) {
val primary =
context.getPrefInt(PreferKey.cNPrimary, context.getCompatColor(R.color.md_blue_grey_600))
val accent =
context.getPrefInt(PreferKey.cNAccent, context.getCompatColor(R.color.md_deep_orange_800))
val background =
context.getPrefInt(PreferKey.cNBackground, context.getCompatColor(R.color.md_grey_900))
val bBackground =
context.getPrefInt(PreferKey.cNBBackground, context.getCompatColor(R.color.md_grey_850))
val config = Config(
themeName = name,
isNightTheme = true,
primaryColor = "#${primary.hexString}",
accentColor = "#${accent.hexString}",
backgroundColor = "#${background.hexString}",
bottomBackground = "#${bBackground.hexString}"
)
addConfig(config)
}
class Config( class Config(
var configName: String = "典雅蓝", var themeName: String = "典雅蓝",
var isNightTheme: Boolean = false, var isNightTheme: Boolean = false,
var primaryColor: String = "#03A9F4", var primaryColor: String = "#03A9F4",
var accentColor: String = "#AD1457", var accentColor: String = "#AD1457",

@ -33,7 +33,7 @@ object Backup {
"txtTocRule.json", "txtTocRule.json",
"readRecord.json", "readRecord.json",
"httpTTS.json", "httpTTS.json",
ReadBookConfig.readConfigFileName, ReadBookConfig.configFileName,
"config.xml" "config.xml"
) )
} }
@ -62,7 +62,7 @@ object Backup {
writeListToJson(App.db.readRecordDao().all, "readRecord.json", backupPath) writeListToJson(App.db.readRecordDao().all, "readRecord.json", backupPath)
writeListToJson(App.db.httpTTSDao().all, "httpTTS.json", backupPath) writeListToJson(App.db.httpTTSDao().all, "httpTTS.json", backupPath)
GSON.toJson(ReadBookConfig.configList)?.let { GSON.toJson(ReadBookConfig.configList)?.let {
FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.readConfigFileName) FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.configFileName)
.writeText(it) .writeText(it)
} }
Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp -> Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp ->

@ -16,6 +16,7 @@ import io.legado.app.data.entities.*
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.LauncherIconHelp import io.legado.app.help.LauncherIconHelp
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.help.ThemeConfig
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.utils.* import io.legado.app.utils.*
@ -158,14 +159,24 @@ object Restore {
suspend fun restoreConfig(path: String = Backup.backupPath) { suspend fun restoreConfig(path: String = Backup.backupPath) {
withContext(IO) { withContext(IO) {
try {
val file =
FileUtils.createFileIfNotExist(path + File.separator + ThemeConfig.configFileName)
if (file.exists()) {
FileUtils.deleteFile(ThemeConfig.configFilePath)
file.copyTo(File(ThemeConfig.configFilePath))
ThemeConfig.upConfig()
}
} catch (e: Exception) {
e.printStackTrace()
}
if (!ignoreReadConfig) { if (!ignoreReadConfig) {
try { try {
val file = val file =
FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.readConfigFileName) FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.configFileName)
val configFile =
FileUtils.getFile(App.INSTANCE.filesDir, ReadBookConfig.readConfigFileName)
if (file.exists()) { if (file.exists()) {
file.copyTo(configFile, true) FileUtils.deleteFile(ReadBookConfig.configFilePath)
file.copyTo(File(ReadBookConfig.configFilePath))
ReadBookConfig.upConfig() ReadBookConfig.upConfig()
} }
} catch (e: Exception) { } catch (e: Exception) {

@ -16,11 +16,18 @@ import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.LauncherIconHelp import io.legado.app.help.LauncherIconHelp
import io.legado.app.help.ThemeConfig
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.ui.widget.number.NumberPickerDialog import io.legado.app.ui.widget.number.NumberPickerDialog
import io.legado.app.ui.widget.prefs.ColorPreference import io.legado.app.ui.widget.prefs.ColorPreference
import io.legado.app.ui.widget.prefs.IconListPreference import io.legado.app.ui.widget.prefs.IconListPreference
import io.legado.app.ui.widget.text.AutoCompleteTextView
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.dialog_edit_text.view.*
@Suppress("SameParameterValue") @Suppress("SameParameterValue")
@ -147,7 +154,7 @@ class ThemeConfigFragment : BasePreferenceFragment(),
@SuppressLint("PrivateResource") @SuppressLint("PrivateResource")
override fun onPreferenceTreeClick(preference: Preference?): Boolean { override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when (preference?.key) { when (val key = preference?.key) {
PreferKey.barElevation -> NumberPickerDialog(requireContext()) PreferKey.barElevation -> NumberPickerDialog(requireContext())
.setTitle(getString(R.string.bar_elevation)) .setTitle(getString(R.string.bar_elevation))
.setMaxValue(32) .setMaxValue(32)
@ -162,17 +169,35 @@ class ThemeConfigFragment : BasePreferenceFragment(),
AppConfig.elevation = it AppConfig.elevation = it
recreateActivities() recreateActivities()
} }
"themeList" -> themeListAlert() "themeList" -> ThemeListDialog().show(childFragmentManager, "themeList")
"saveDayTheme" -> { "saveDayTheme", "saveNightTheme" -> saveThemeAlert(key)
}
"saveNightTheme" -> {
}
} }
return super.onPreferenceTreeClick(preference) return super.onPreferenceTreeClick(preference)
} }
private fun themeListAlert() { @SuppressLint("InflateParams")
private fun saveThemeAlert(key: String) {
alert("主题名称") {
var editText: AutoCompleteTextView? = null
customView {
layoutInflater.inflate(R.layout.dialog_edit_text, null).apply {
editText = edit_view
}
}
okButton {
editText?.text?.toString()?.let { themeName ->
when (key) {
"saveDayTheme" -> {
ThemeConfig.saveDayTheme(requireContext(), themeName)
}
"saveNightTheme" -> {
ThemeConfig.saveNightTheme(requireContext(), themeName)
}
}
}
}
noButton { }
}.show().applyTint()
} }
private fun upTheme(isNightTheme: Boolean) { private fun upTheme(isNightTheme: Boolean) {

@ -0,0 +1,98 @@
package io.legado.app.ui.config
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.help.ThemeConfig
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.GSON
import io.legado.app.utils.applyTint
import kotlinx.android.synthetic.main.dialog_recycler_view.*
import kotlinx.android.synthetic.main.item_theme_config.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
import org.jetbrains.anko.share
class ThemeListDialog : BaseDialogFragment() {
private lateinit var adapter: Adapter
override fun onStart() {
super.onStart()
val dm = DisplayMetrics()
activity?.windowManager?.defaultDisplay?.getMetrics(dm)
dialog?.window?.setLayout((dm.widthPixels * 0.9).toInt(), (dm.heightPixels * 0.9).toInt())
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.dialog_recycler_view, container)
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
tool_bar.setBackgroundColor(primaryColor)
tool_bar.setTitle(R.string.theme_list)
initView()
initData()
}
private fun initView() {
adapter = Adapter()
recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(VerticalDivider(requireContext()))
recycler_view.adapter = adapter
}
fun initData() {
adapter.setItems(ThemeConfig.configList)
}
fun delete(index: Int) {
alert(R.string.delete, R.string.sure_del) {
okButton {
ThemeConfig.configList.removeAt(index)
initData()
}
noButton()
}.show().applyTint()
}
fun share(index: Int) {
val json = GSON.toJson(ThemeConfig.configList[index])
requireContext().share(json, "主题分享")
}
inner class Adapter : SimpleRecyclerAdapter<ThemeConfig.Config>(requireContext(), R.layout.item_theme_config) {
override fun convert(holder: ItemViewHolder, item: ThemeConfig.Config, payloads: MutableList<Any>) {
holder.itemView.apply {
tv_name.text = item.themeName
}
}
override fun registerListener(holder: ItemViewHolder) {
holder.itemView.apply {
iv_share.onClick {
share(holder.layoutPosition)
}
iv_delete.onClick {
delete(holder.layoutPosition)
}
}
}
}
}

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="1" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_share"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/edit"
android:padding="6dp"
android:tooltipText="@string/edit"
android:src="@drawable/ic_share"
android:tint="@color/primaryText"
tools:ignore="UnusedAttribute" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_delete"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/more_menu"
android:tooltipText="@string/more_menu"
android:padding="6dp"
android:src="@drawable/ic_clear_all"
android:tint="@color/primaryText"
tools:ignore="UnusedAttribute" />
</LinearLayout>
Loading…
Cancel
Save