Merge pull request #2 from gedoor/master

更新
pull/282/head
10bits 4 years ago committed by GitHub
commit b30b9197e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      app/build.gradle
  2. 5
      app/src/main/assets/updateLog.md
  3. 4
      app/src/main/java/io/legado/app/App.kt
  4. 13
      app/src/main/java/io/legado/app/base/BaseDialogFragment.kt
  5. 1
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  6. 3
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  7. 5
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  8. 84
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  9. 11
      app/src/main/java/io/legado/app/lib/dialogs/AlertBuilder.kt
  10. 15
      app/src/main/java/io/legado/app/lib/dialogs/AndroidAlertBuilder.kt
  11. 16
      app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt
  12. 3
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  13. 11
      app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
  14. 14
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
  15. 26
      app/src/main/java/io/legado/app/ui/widget/font/FontAdapter.kt
  16. 87
      app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt
  17. 2
      app/src/main/java/io/legado/app/utils/DocumentUtils.kt
  18. 13
      app/src/main/java/io/legado/app/utils/FileUtils.kt
  19. 10
      app/src/main/java/io/legado/app/utils/StringExtensions.kt
  20. 12
      app/src/main/res/layout/view_read_menu.xml
  21. 3
      app/src/main/res/values-zh-rHK/strings.xml
  22. 3
      app/src/main/res/values-zh-rTW/strings.xml
  23. 3
      app/src/main/res/values-zh/strings.xml
  24. 6
      app/src/main/res/values/arrays.xml
  25. 41
      app/src/main/res/values/strings.xml
  26. 7
      app/src/main/res/xml/pref_config_backup.xml

@ -115,6 +115,7 @@ dependencies {
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation "com.android.support:multidex:1.0.3"
//kotlin //kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

@ -3,6 +3,11 @@
* 请关注公众号[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 * 请关注公众号[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。
* 新公众号[开源阅读]()已启用,[开源阅读软件]()备用 * 新公众号[开源阅读]()已启用,[开源阅读软件]()备用
**2020/07/21**
* 优化文字选择,不再缓存
* 添加忽略恢复列表,方便不同手机配置不同
* 其它一些bug修复
**2020/07/19** **2020/07/19**
* 添加自定义默认封面 * 添加自定义默认封面
* 修复封面选择本地图片时书架不显示的bug * 修复封面选择本地图片时书架不显示的bug

@ -1,6 +1,5 @@
package io.legado.app package io.legado.app
import android.app.Application
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
@ -9,6 +8,7 @@ import android.graphics.Color
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDexApplication
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import io.legado.app.constant.AppConst.channelIdDownload import io.legado.app.constant.AppConst.channelIdDownload
import io.legado.app.constant.AppConst.channelIdReadAloud import io.legado.app.constant.AppConst.channelIdReadAloud
@ -28,7 +28,7 @@ import io.legado.app.utils.postEvent
import io.legado.app.utils.putPrefInt import io.legado.app.utils.putPrefInt
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
class App : Application() { class App : MultiDexApplication() {
companion object { companion object {
@JvmStatic @JvmStatic

@ -3,6 +3,7 @@ package io.legado.app.base
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.theme.ThemeStore import io.legado.app.lib.theme.ThemeStore
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -10,6 +11,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
abstract class BaseDialogFragment : DialogFragment(), CoroutineScope { abstract class BaseDialogFragment : DialogFragment(), CoroutineScope {
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main get() = job + Dispatchers.Main
@ -29,6 +31,17 @@ abstract class BaseDialogFragment : DialogFragment(), CoroutineScope {
abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?) abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)
override fun show(manager: FragmentManager, tag: String?) {
try {
//在每个add事务前增加一个remove事务,防止连续的add
manager.beginTransaction().remove(this).commit()
super.show(manager, tag)
} catch (e: Exception) {
//同一实例使用不同的tag会异常,这里捕获一下
e.printStackTrace()
}
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
job.cancel() job.cancel()

@ -25,6 +25,7 @@ object PreferKey {
const val readBookFont = "readBookFont" const val readBookFont = "readBookFont"
const val fontFolder = "fontFolder" const val fontFolder = "fontFolder"
const val backupPath = "backupUri" const val backupPath = "backupUri"
const val restoreIgnore = "restoreIgnore"
const val threadCount = "threadCount" const val threadCount = "threadCount"
const val webPort = "webPort" const val webPort = "webPort"
const val keepLight = "keep_light" const val keepLight = "keep_light"

@ -19,8 +19,7 @@ import java.io.File
@Keep @Keep
object ReadBookConfig { object ReadBookConfig {
const val readConfigFileName = "readConfig.json" const val readConfigFileName = "readConfig.json"
private val configFilePath = private val configFilePath = FileUtils.getPath(App.INSTANCE.filesDir, readConfigFileName)
App.INSTANCE.filesDir.absolutePath + File.separator + readConfigFileName
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(readConfigFileName).readBytes())

@ -1,7 +1,5 @@
package io.legado.app.help.http package io.legado.app.help.http
import com.franmontiel.persistentcookiejar.PersistentCookieJar
import com.franmontiel.persistentcookiejar.cache.SetCookieCache
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.help.http.api.HttpGetApi import io.legado.app.help.http.api.HttpGetApi
import io.legado.app.utils.NetworkUtils import io.legado.app.utils.NetworkUtils
@ -19,8 +17,6 @@ object HttpHelper {
val client: OkHttpClient by lazy { val client: OkHttpClient by lazy {
val cookieJar = PersistentCookieJar(SetCookieCache(), CookieStore)
val specs = arrayListOf( val specs = arrayListOf(
ConnectionSpec.MODERN_TLS, ConnectionSpec.MODERN_TLS,
ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.COMPATIBLE_TLS,
@ -39,7 +35,6 @@ object HttpHelper {
.followSslRedirects(true) .followSslRedirects(true)
.protocols(listOf(Protocol.HTTP_1_1)) .protocols(listOf(Protocol.HTTP_1_1))
.addInterceptor(getHeaderInterceptor()) .addInterceptor(getHeaderInterceptor())
.cookieJar(cookieJar)
builder.build() builder.build()
} }

@ -9,6 +9,7 @@ import com.jayway.jsonpath.Option
import com.jayway.jsonpath.ParseContext import com.jayway.jsonpath.ParseContext
import io.legado.app.App import io.legado.app.App
import io.legado.app.BuildConfig import io.legado.app.BuildConfig
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.data.entities.* import io.legado.app.data.entities.*
@ -24,6 +25,42 @@ import org.jetbrains.anko.defaultSharedPreferences
import java.io.File import java.io.File
object Restore { object Restore {
private val ignoreConfigPath = FileUtils.getPath(App.INSTANCE.filesDir, "restoreIgnore.json")
val ignoreConfig: HashMap<String, Boolean> by lazy {
val file = FileUtils.createFileIfNotExist(ignoreConfigPath)
val json = file.readText()
GSON.fromJsonObject<HashMap<String, Boolean>>(json) ?: hashMapOf()
}
//忽略key
val ignoreKeys = arrayOf(
"readConfig",
PreferKey.themeMode,
PreferKey.bookshelfLayout
)
//忽略标题
val ignoreTitle = arrayOf(
App.INSTANCE.getString(R.string.read_config),
App.INSTANCE.getString(R.string.theme_mode),
App.INSTANCE.getString(R.string.bookshelf_layout)
)
//默认忽略keys
private val ignorePrefKeys = arrayOf(
PreferKey.versionCode,
PreferKey.defaultCover
)
private val readPrefKeys = arrayOf(
PreferKey.readStyleSelect,
PreferKey.shareLayout,
PreferKey.pageAnim,
PreferKey.hideStatusBar,
PreferKey.hideNavigationBar,
PreferKey.bodyIndent,
PreferKey.autoReadSpeed
)
val jsonPath: ParseContext by lazy { val jsonPath: ParseContext by lazy {
JsonPath.using( JsonPath.using(
Configuration.builder() Configuration.builder()
@ -92,24 +129,25 @@ object Restore {
suspend fun restoreConfig(path: String = Backup.backupPath) { suspend fun restoreConfig(path: String = Backup.backupPath) {
withContext(IO) { withContext(IO) {
try { if (!ignoreReadConfig) {
val file = try {
FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.readConfigFileName) val file =
val configFile = FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.readConfigFileName)
FileUtils.getFile(App.INSTANCE.filesDir, ReadBookConfig.readConfigFileName) val configFile =
if (file.exists()) { FileUtils.getFile(App.INSTANCE.filesDir, ReadBookConfig.readConfigFileName)
file.copyTo(configFile, true) if (file.exists()) {
ReadBookConfig.upConfig() file.copyTo(configFile, true)
ReadBookConfig.upConfig()
}
} catch (e: Exception) {
e.printStackTrace()
} }
} catch (e: Exception) {
e.printStackTrace()
} }
Preferences.getSharedPreferences(App.INSTANCE, path, "config")?.all Preferences.getSharedPreferences(App.INSTANCE, path, "config")?.all
?.let { map -> ?.let { map ->
val ignoreKeys = arrayOf(PreferKey.versionCode, PreferKey.defaultCover)
val edit = App.INSTANCE.defaultSharedPreferences.edit() val edit = App.INSTANCE.defaultSharedPreferences.edit()
map.forEach { map.forEach {
if (!ignoreKeys.contains(it.key)) { if (keyIsNotIgnore(it.key)) {
when (val value = it.value) { when (val value = it.value) {
is Int -> edit.putInt(it.key, value) is Int -> edit.putInt(it.key, value)
is Boolean -> edit.putBoolean(it.key, value) is Boolean -> edit.putBoolean(it.key, value)
@ -143,6 +181,28 @@ object Restore {
} }
} }
private fun keyIsNotIgnore(key: String): Boolean {
return when {
ignorePrefKeys.contains(key) -> false
readPrefKeys.contains(key) && ignoreReadConfig -> false
PreferKey.themeMode == key && ignoreThemeMode -> false
PreferKey.bookshelfLayout == key && ignoreBookshelfLayout -> false
else -> true
}
}
private val ignoreReadConfig: Boolean
get() = ignoreConfig["readConfig"] == true
private val ignoreThemeMode: Boolean
get() = ignoreConfig[PreferKey.themeMode] == true
private val ignoreBookshelfLayout: Boolean
get() = ignoreConfig[PreferKey.bookshelfLayout] == true
fun saveIgnoreConfig() {
val json = GSON.toJson(ignoreConfig)
FileUtils.createFileIfNotExist(ignoreConfigPath).writeText(json)
}
private inline fun <reified T> fileToListT(path: String, fileName: String): List<T>? { private inline fun <reified T> fileToListT(path: String, fileName: String): List<T>? {
try { try {
val file = FileUtils.createFileIfNotExist(path + File.separator + fileName) val file = FileUtils.createFileIfNotExist(path + File.separator + fileName)

@ -75,7 +75,16 @@ interface AlertBuilder<out D : DialogInterface> {
fun onKeyPressed(handler: (dialog: DialogInterface, keyCode: Int, e: KeyEvent) -> Boolean) fun onKeyPressed(handler: (dialog: DialogInterface, keyCode: Int, e: KeyEvent) -> Boolean)
fun items(items: List<CharSequence>, onItemSelected: (dialog: DialogInterface, index: Int) -> Unit) fun items(items: List<CharSequence>, onItemSelected: (dialog: DialogInterface, index: Int) -> Unit)
fun <T> items(items: List<T>, onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit) fun <T> items(
items: List<T>,
onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit
)
fun multiChoiceItems(
items: Array<String>,
checkedItems: BooleanArray,
onClick: (dialog: DialogInterface, which: Int, isChecked: Boolean) -> Unit
)
fun build(): D fun build(): D
fun show(): D fun show(): D

@ -105,12 +105,25 @@ internal class AndroidAlertBuilder(override val ctx: Context) : AlertBuilder<Ale
} }
} }
override fun <T> items(items: List<T>, onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit) { override fun <T> items(
items: List<T>,
onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit
) {
builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which -> builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which ->
onItemSelected(dialog, items[which], which) onItemSelected(dialog, items[which], which)
} }
} }
override fun multiChoiceItems(
items: Array<String>,
checkedItems: BooleanArray,
onClick: (dialog: DialogInterface, which: Int, isChecked: Boolean) -> Unit
) {
builder.setMultiChoiceItems(items, checkedItems) { dialog, which, isChecked ->
onClick(dialog, which, isChecked)
}
}
override fun build(): AlertDialog = builder.create() override fun build(): AlertDialog = builder.create()
override fun show(): AlertDialog = builder.show() override fun show(): AlertDialog = builder.show()

@ -11,6 +11,8 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.storage.Restore
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
@ -104,6 +106,7 @@ class BackupConfigFragment : PreferenceFragmentCompat(),
override fun onPreferenceTreeClick(preference: Preference?): Boolean { override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when (preference?.key) { when (preference?.key) {
PreferKey.backupPath -> BackupRestoreUi.selectBackupFolder(this) PreferKey.backupPath -> BackupRestoreUi.selectBackupFolder(this)
PreferKey.restoreIgnore -> restoreIgnore()
"web_dav_backup" -> BackupRestoreUi.backup(this) "web_dav_backup" -> BackupRestoreUi.backup(this)
"web_dav_restore" -> BackupRestoreUi.restore(this) "web_dav_restore" -> BackupRestoreUi.restore(this)
"import_old" -> BackupRestoreUi.importOldData(this) "import_old" -> BackupRestoreUi.importOldData(this)
@ -111,6 +114,19 @@ class BackupConfigFragment : PreferenceFragmentCompat(),
return super.onPreferenceTreeClick(preference) return super.onPreferenceTreeClick(preference)
} }
private fun restoreIgnore() {
val checkedItems = BooleanArray(Restore.ignoreKeys.size) {
Restore.ignoreConfig[Restore.ignoreKeys[it]] ?: false
}
alert(R.string.restore_ignore) {
multiChoiceItems(Restore.ignoreTitle, checkedItems) { _, which, isChecked ->
Restore.ignoreConfig[Restore.ignoreKeys[which]] = isChecked
}
}.show().setOnDismissListener {
Restore.saveIgnoreConfig()
}
}
override fun onFilePicked(requestCode: Int, currentPath: String) { override fun onFilePicked(requestCode: Int, currentPath: String) {
BackupRestoreUi.onFilePicked(requestCode, currentPath) BackupRestoreUi.onFilePicked(requestCode, currentPath)
} }

@ -64,6 +64,9 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
viewModel.upChapterList() viewModel.upChapterList()
}, 1000) }, 1000)
} }
view_pager_main.postDelayed({
viewModel.postLoad()
}, 3000)
} }
override fun onNavigationItemSelected(item: MenuItem): Boolean { override fun onNavigationItemSelected(item: MenuItem): Boolean {

@ -10,6 +10,7 @@ import io.legado.app.data.entities.RssSource
import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.HttpHelper
import io.legado.app.help.storage.Restore import io.legado.app.help.storage.Restore
import io.legado.app.model.WebBook import io.legado.app.model.WebBook
import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
@ -77,4 +78,14 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
} }
} }
} }
fun postLoad() {
execute {
FileUtils.getDirFile(context.cacheDir, "Fonts").let {
if (it.exists()) {
it.delete()
}
}
}
}
} }

@ -88,18 +88,18 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
R.id.menu_add_local -> startActivity<ImportBookActivity>() R.id.menu_add_local -> startActivity<ImportBookActivity>()
R.id.menu_add_url -> addBookByUrl() R.id.menu_add_url -> addBookByUrl()
R.id.menu_arrange_bookshelf -> startActivity<ArrangeBookActivity>( R.id.menu_arrange_bookshelf -> startActivity<ArrangeBookActivity>(
Pair("groupId", selectedGroup.groupId), Pair("groupId", selectedGroup?.groupId ?: 0),
Pair("groupName", selectedGroup.groupName) Pair("groupName", selectedGroup?.groupName ?: 0)
) )
R.id.menu_download -> startActivity<DownloadActivity>( R.id.menu_download -> startActivity<DownloadActivity>(
Pair("groupId", selectedGroup.groupId), Pair("groupId", selectedGroup?.groupId ?: 0),
Pair("groupName", selectedGroup.groupName) Pair("groupName", selectedGroup?.groupName ?: 0)
) )
} }
} }
private val selectedGroup: BookGroup private val selectedGroup: BookGroup?
get() = bookGroups[view_pager_bookshelf.currentItem] get() = bookGroups.getOrNull(view_pager_bookshelf?.currentItem ?: 0)
private fun initView() { private fun initView() {
ATH.applyEdgeEffectColor(view_pager_bookshelf) ATH.applyEdgeEffectColor(view_pager_bookshelf)
@ -267,7 +267,7 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
} }
fun gotoTop() { fun gotoTop() {
fragmentMap[selectedGroup.groupId]?.gotoTop() fragmentMap[selectedGroup?.groupId]?.gotoTop()
} }
private inner class TabFragmentPageAdapter internal constructor(fm: FragmentManager) : private inner class TabFragmentPageAdapter internal constructor(fm: FragmentManager) :

@ -2,9 +2,12 @@ package io.legado.app.ui.widget.font
import android.content.Context import android.content.Context
import android.graphics.Typeface import android.graphics.Typeface
import android.os.Build
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.utils.DocItem
import io.legado.app.utils.RealPathUtil
import io.legado.app.utils.invisible import io.legado.app.utils.invisible
import io.legado.app.utils.visible import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.item_font.view.* import kotlinx.android.synthetic.main.item_font.view.*
@ -13,15 +16,28 @@ import org.jetbrains.anko.toast
import java.io.File import java.io.File
class FontAdapter(context: Context, val callBack: CallBack) : class FontAdapter(context: Context, val callBack: CallBack) :
SimpleRecyclerAdapter<File>(context, R.layout.item_font) { SimpleRecyclerAdapter<DocItem>(context, R.layout.item_font) {
override fun convert(holder: ItemViewHolder, item: File, payloads: MutableList<Any>) { override fun convert(holder: ItemViewHolder, item: DocItem, payloads: MutableList<Any>) {
with(holder.itemView) { with(holder.itemView) {
try { try {
val typeface = Typeface.createFromFile(item) val typeface: Typeface? = if (item.isContentPath) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.contentResolver
.openFileDescriptor(item.uri, "r")
?.fileDescriptor?.let {
Typeface.Builder(it).build()
}
} else {
Typeface.createFromFile(RealPathUtil.getPath(context, item.uri))
}
} else {
Typeface.createFromFile(item.uri.toString())
}
tv_font.typeface = typeface tv_font.typeface = typeface
} catch (e: Exception) { } catch (e: Exception) {
context.toast("读取${item.name}字体失败") e.printStackTrace()
context.toast("Read ${item.name} Error: ${e.localizedMessage}")
} }
tv_font.text = item.name tv_font.text = item.name
this.onClick { callBack.onClick(item) } this.onClick { callBack.onClick(item) }
@ -42,7 +58,7 @@ class FontAdapter(context: Context, val callBack: CallBack) :
} }
interface CallBack { interface CallBack {
fun onClick(file: File) fun onClick(docItem: DocItem)
val curFilePath: String val curFilePath: String
} }
} }

@ -26,11 +26,11 @@ import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.dialog_font_select.* import kotlinx.android.synthetic.main.dialog_font_select.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.util.*
class FontSelectDialog : BaseDialogFragment(), class FontSelectDialog : BaseDialogFragment(),
FileChooserDialog.CallBack, FileChooserDialog.CallBack,
@ -40,9 +40,6 @@ class FontSelectDialog : BaseDialogFragment(),
private val fontFolder by lazy { private val fontFolder by lazy {
FileUtils.createFolderIfNotExist(App.INSTANCE.filesDir, "Fonts") FileUtils.createFolderIfNotExist(App.INSTANCE.filesDir, "Fonts")
} }
private val fontCacheFolder by lazy {
FileUtils.createFolderIfNotExist(App.INSTANCE.cacheDir, "Fonts")
}
private var adapter: FontAdapter? = null private var adapter: FontAdapter? = null
override fun onStart() { override fun onStart() {
@ -90,7 +87,9 @@ class FontSelectDialog : BaseDialogFragment(),
R.id.menu_default -> { R.id.menu_default -> {
val requireContext = requireContext() val requireContext = requireContext()
requireContext.alert(titleResource = R.string.system_typeface) { requireContext.alert(titleResource = R.string.system_typeface) {
items(requireContext.resources.getStringArray(R.array.system_typefaces).toList()) { _, i -> items(
requireContext.resources.getStringArray(R.array.system_typefaces).toList()
) { _, i ->
AppConfig.systemTypefaces = i AppConfig.systemTypefaces = i
onDefaultFontChange() onDefaultFontChange()
dismiss() dismiss()
@ -117,40 +116,16 @@ class FontSelectDialog : BaseDialogFragment(),
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
private fun getFontFiles(doc: DocumentFile) { private fun getFontFiles(doc: DocumentFile) {
execute { execute {
val fontItems = arrayListOf<DocItem>()
val docItems = DocumentUtils.listFiles(App.INSTANCE, doc.uri) val docItems = DocumentUtils.listFiles(App.INSTANCE, doc.uri)
fontCacheFolder.listFiles()?.forEach { fontFile ->
var contain = false
for (item in docItems) {
if (fontFile.name == item.name) {
contain = true
break
}
}
if (!contain) {
fontFile.delete()
}
}
docItems.forEach { item -> docItems.forEach { item ->
if (item.name.toLowerCase().matches(".*\\.[ot]tf".toRegex())) { if (item.name.toLowerCase().matches(".*\\.[ot]tf".toRegex())) {
val fontFile = FileUtils.getFile(fontCacheFolder, item.name) fontItems.add(item)
if (!fontFile.exists()) {
DocumentUtils.readBytes(App.INSTANCE, item.uri)?.let { byteArray ->
fontFile.writeBytes(byteArray)
}
}
}
}
try {
fontCacheFolder.listFiles { pathName ->
pathName.name.toLowerCase().matches(".*\\.[ot]tf".toRegex())
}?.let {
withContext(Main) {
adapter?.setItems(it.toList())
}
} }
} catch (e: Exception) {
toast(e.localizedMessage ?: "")
} }
fontItems
}.onSuccess {
adapter?.setItems(it)
}.onError { }.onError {
toast("getFontFiles:${it.localizedMessage}") toast("getFontFiles:${it.localizedMessage}")
} }
@ -163,12 +138,22 @@ class FontSelectDialog : BaseDialogFragment(),
.rationale(R.string.tip_perm_request_storage) .rationale(R.string.tip_perm_request_storage)
.onGranted { .onGranted {
try { try {
val fontItems = arrayListOf<DocItem>()
val file = File(path) val file = File(path)
file.listFiles { pathName -> file.listFiles { pathName ->
pathName.name.toLowerCase().matches(".*\\.[ot]tf".toRegex()) pathName.name.toLowerCase().matches(".*\\.[ot]tf".toRegex())
}?.let { }?.forEach {
adapter?.setItems(it.toList()) fontItems.add(
DocItem(
it.name,
it.extension,
it.length(),
Date(it.lastModified()),
Uri.parse(it.absolutePath)
)
)
} }
adapter?.setItems(fontItems)
} catch (e: Exception) { } catch (e: Exception) {
toast(e.localizedMessage ?: "") toast(e.localizedMessage ?: "")
} }
@ -176,16 +161,32 @@ class FontSelectDialog : BaseDialogFragment(),
.request() .request()
} }
override fun onClick(file: File) { override fun onClick(docItem: DocItem) {
launch(IO) { execute {
file.copyTo(FileUtils.createFileIfNotExist(fontFolder, file.name), true) fontFolder.listFiles()?.forEach {
.absolutePath.let { path -> it.delete()
if (curFilePath != path) { }
withContext(Main) { if (docItem.isContentPath) {
callBack?.selectFile(path) val file = FileUtils.createFileIfNotExist(fontFolder, docItem.name)
@Suppress("BlockingMethodInNonBlockingContext")
requireActivity().contentResolver.openInputStream(docItem.uri)?.use { input ->
file.outputStream().use { output ->
input.copyTo(output)
} }
} }
callBack?.selectFile(file.path)
} else {
val file = File(docItem.uri.toString())
file.copyTo(FileUtils.createFileIfNotExist(fontFolder, file.name), true)
.absolutePath.let { path ->
if (curFilePath != path) {
withContext(Main) {
callBack?.selectFile(path)
}
}
}
} }
}.onSuccess {
dialog?.dismiss() dialog?.dismiss()
} }
} }

@ -138,6 +138,8 @@ data class DocItem(
val isDir: Boolean by lazy { val isDir: Boolean by lazy {
DocumentsContract.Document.MIME_TYPE_DIR == attr DocumentsContract.Document.MIME_TYPE_DIR == attr
} }
val isContentPath get() = uri.toString().isContentPath()
} }
@Throws(Exception::class) @Throws(Exception::class)

@ -18,7 +18,7 @@ object FileUtils {
} }
fun createFolderIfNotExist(root: File, vararg subDirs: String): File { fun createFolderIfNotExist(root: File, vararg subDirs: String): File {
val filePath = root.absolutePath + File.separator + subDirs.joinToString(File.separator) val filePath = getPath(root, subDirs = *subDirs)
return createFolderIfNotExist(filePath) return createFolderIfNotExist(filePath)
} }
@ -60,11 +60,14 @@ object FileUtils {
} }
fun getPath(root: File, fileName: String? = null, vararg subDirs: String): String { fun getPath(root: File, fileName: String? = null, vararg subDirs: String): String {
return if (fileName.isNullOrEmpty()) { val path = StringBuilder(root.absolutePath)
root.absolutePath + File.separator + subDirs.joinToString(File.separator) subDirs.forEach {
} else { path.append(File.separator).append(it)
root.absolutePath + File.separator + subDirs.joinToString(File.separator) + File.separator + fileName }
if (!fileName.isNullOrEmpty()) {
path.append(File.separator).append(fileName)
} }
return path.toString()
} }
//递归删除文件夹下的数据 //递归删除文件夹下的数据

@ -1,7 +1,8 @@
package io.legado.app.utils package io.legado.app.utils
val removeHtmlRegex = val removeHtmlRegex = "</?(?:div|p|br|hr|h\\d|article|dd|dl)[^>]*>".toRegex()
"</?(?:html|head|div|a|p|b|br|hr|h\\d|article|dd|dl|span|link|title)[^>]*>".toRegex() val imgRegex = "<img[^>]*>".toRegex()
val notImgHtmlRegex = "</?(?!img)\\w+[^>]*>".toRegex()
fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim() fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim()
@ -37,7 +38,10 @@ fun String?.isJsonArray(): Boolean =
fun String?.htmlFormat(): String { fun String?.htmlFormat(): String {
this ?: return "" this ?: return ""
return this.replace(removeHtmlRegex, "\n") return this
.replace(imgRegex, "\n$0\n")
.replace(removeHtmlRegex, "\n")
.replace(notImgHtmlRegex, "")
.replace("\\s*\\n+\\s*".toRegex(), "\n  ") .replace("\\s*\\n+\\s*".toRegex(), "\n  ")
.replace("^[\\n\\s]+".toRegex(), "  ") .replace("^[\\n\\s]+".toRegex(), "  ")
.replace("[\\n\\s]+$".toRegex(), "") .replace("[\\n\\s]+$".toRegex(), "")

@ -222,7 +222,7 @@
<!--目录按钮--> <!--目录按钮-->
<LinearLayout <LinearLayout
android:id="@+id/ll_catalog" android:id="@+id/ll_catalog"
android:layout_width="50dp" android:layout_width="60dp"
android:layout_height="50dp" android:layout_height="50dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true" android:clickable="true"
@ -245,6 +245,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:maxLines="1"
android:text="@string/chapter_list" android:text="@string/chapter_list"
android:textColor="@color/tv_text_default" android:textColor="@color/tv_text_default"
android:textSize="12sp" /> android:textSize="12sp" />
@ -257,7 +258,7 @@
<!--调节按钮--> <!--调节按钮-->
<LinearLayout <LinearLayout
android:id="@+id/ll_read_aloud" android:id="@+id/ll_read_aloud"
android:layout_width="50dp" android:layout_width="60dp"
android:layout_height="50dp" android:layout_height="50dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true" android:clickable="true"
@ -280,6 +281,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:maxLines="1"
android:text="@string/read_aloud" android:text="@string/read_aloud"
android:textColor="@color/tv_text_default" android:textColor="@color/tv_text_default"
android:textSize="12sp" /> android:textSize="12sp" />
@ -292,7 +294,7 @@
<!--界面按钮--> <!--界面按钮-->
<LinearLayout <LinearLayout
android:id="@+id/ll_font" android:id="@+id/ll_font"
android:layout_width="50dp" android:layout_width="60dp"
android:layout_height="50dp" android:layout_height="50dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true" android:clickable="true"
@ -315,6 +317,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:maxLines="1"
android:text="@string/interface_setting" android:text="@string/interface_setting"
android:textColor="@color/tv_text_default" android:textColor="@color/tv_text_default"
android:textSize="12sp" /> android:textSize="12sp" />
@ -327,7 +330,7 @@
<!--设置按钮--> <!--设置按钮-->
<LinearLayout <LinearLayout
android:id="@+id/ll_setting" android:id="@+id/ll_setting"
android:layout_width="50dp" android:layout_width="60dp"
android:layout_height="50dp" android:layout_height="50dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true" android:clickable="true"
@ -350,6 +353,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp" android:layout_marginTop="3dp"
android:maxLines="1"
android:text="@string/setting" android:text="@string/setting"
android:textColor="@color/tv_text_default" android:textColor="@color/tv_text_default"
android:textSize="12sp" /> android:textSize="12sp" />

@ -721,4 +721,7 @@
<string name="contributors_summary">gedoor,Invinciblelee等,详情请在github中查看</string> <string name="contributors_summary">gedoor,Invinciblelee等,详情请在github中查看</string>
<string name="clear_cache_summary">清除已下载书籍和字体缓存</string> <string name="clear_cache_summary">清除已下载书籍和字体缓存</string>
<string name="default_cover">默认封面</string> <string name="default_cover">默认封面</string>
<string name="restore_ignore">恢复忽略列表</string>
<string name="restore_ignore_summary">恢复时忽略一些内容不恢复,方便不同手机配置不同</string>
<string name="read_config">阅读界面设置</string>
</resources> </resources>

@ -721,5 +721,8 @@
<string name="contributors_summary">gedoor,Invinciblelee等,详情请在github中查看</string> <string name="contributors_summary">gedoor,Invinciblelee等,详情请在github中查看</string>
<string name="clear_cache_summary">清除已下载书籍和字体缓存</string> <string name="clear_cache_summary">清除已下载书籍和字体缓存</string>
<string name="default_cover">默认封面</string> <string name="default_cover">默认封面</string>
<string name="restore_ignore">恢复忽略列表</string>
<string name="restore_ignore_summary">恢复时忽略一些内容不恢复,方便不同手机配置不同</string>
<string name="read_config">阅读界面设置</string>
</resources> </resources>

@ -721,4 +721,7 @@
<string name="contributors_summary">gedoor,Invinciblelee等,详情请在github中查看</string> <string name="contributors_summary">gedoor,Invinciblelee等,详情请在github中查看</string>
<string name="clear_cache_summary">清除已下载书籍和字体缓存</string> <string name="clear_cache_summary">清除已下载书籍和字体缓存</string>
<string name="default_cover">默认封面</string> <string name="default_cover">默认封面</string>
<string name="restore_ignore">恢复忽略列表</string>
<string name="restore_ignore_summary">恢复时忽略一些内容不恢复,方便不同手机配置不同</string>
<string name="read_config">阅读界面设置</string>
</resources> </resources>

@ -42,9 +42,9 @@
<string-array name="theme_mode"> <string-array name="theme_mode">
<item>Follow system</item> <item>Follow system</item>
<item>Bright theme</item> <item>Bright mode</item>
<item>Dark theme</item> <item>Dark mode</item>
<item>E-Ink</item> <item>E-Ink mode</item>
</string-array> </string-array>
<string-array name="NavBarColors"> <string-array name="NavBarColors">

@ -218,7 +218,7 @@
<string name="import_select_book">Import selected book(s)</string> <string name="import_select_book">Import selected book(s)</string>
<string name="threads_num_title">Number of concurrent tasks</string> <string name="threads_num_title">Number of concurrent tasks</string>
<string name="change_icon">Change icon</string> <string name="change_icon">Change icon</string>
<string name="remove_from_bookshelf">Remove books</string> <string name="remove_from_bookshelf">Remove</string>
<string name="start_read">Start reading</string> <string name="start_read">Start reading</string>
<string name="data_loading">Loading…</string> <string name="data_loading">Loading…</string>
<string name="load_error_retry">Load failed, tap to retry</string> <string name="load_error_retry">Load failed, tap to retry</string>
@ -332,7 +332,7 @@
<string name="tip_margin_change">Margin adjustment</string> <string name="tip_margin_change">Margin adjustment</string>
<string name="allow_update">Allow update</string> <string name="allow_update">Allow update</string>
<string name="disable_update">Disable update</string> <string name="disable_update">Disable update</string>
<string name="revert_selection">Invert selection</string> <string name="revert_selection">Inverse</string>
<string name="search_book_key">Search bookName/author</string> <string name="search_book_key">Search bookName/author</string>
<string name="debug_hint">BookName,Author,URL</string> <string name="debug_hint">BookName,Author,URL</string>
<string name="faq">FAQ</string> <string name="faq">FAQ</string>
@ -586,8 +586,8 @@
<string name="bottom_line">I AM OVER!</string> <string name="bottom_line">I AM OVER!</string>
<string name="uri_to_path_fail">Uri To Path failed</string> <string name="uri_to_path_fail">Uri To Path failed</string>
<string name="refresh_cover">Refresh cover</string> <string name="refresh_cover">Refresh cover</string>
<string name="change_cover_source">Change cover\'s origin</string> <string name="change_cover_source">Change origin</string>
<string name="select_local_image">Select a local image</string> <string name="select_local_image">Local image</string>
<string name="book_type">Type:</string> <string name="book_type">Type:</string>
<string name="book_type_text">Text</string> <string name="book_type_text">Text</string>
<string name="book_type_audio">Audio</string> <string name="book_type_audio">Audio</string>
@ -652,7 +652,7 @@
<string name="save_image">Save image</string> <string name="save_image">Save image</string>
<string name="no_default_path">No default path</string> <string name="no_default_path">No default path</string>
<string name="change_group">Group settings</string> <string name="change_group">Group settings</string>
<string name="view_toc">View changes</string> <string name="view_toc">View Chapters</string>
<string name="bar_elevation">Navigation bar shadow</string> <string name="bar_elevation">Navigation bar shadow</string>
<string name="bar_elevation_s">Current shadow size(elevation): %s</string> <string name="bar_elevation_s">Current shadow size(elevation): %s</string>
<string name="btn_default_s">Default</string> <string name="btn_default_s">Default</string>
@ -697,13 +697,13 @@
<string name="night_accent">Night,Accent</string> <string name="night_accent">Night,Accent</string>
<string name="night_background_color">Night,Background color</string> <string name="night_background_color">Night,Background color</string>
<string name="night_navbar_color">Night,NavBar color</string> <string name="night_navbar_color">Night,NavBar color</string>
<string name="auto_change_source">Changing source automatically</string> <string name="auto_change_source">Change source automatically</string>
<string name="text_full_justify">Text justified</string> <string name="text_full_justify">Text justified</string>
<string name="text_bottom_justify">Text align bottom</string> <string name="text_bottom_justify">Text align bottom</string>
<string name="auto_page_speed">Auto scroll speed</string> <string name="auto_page_speed">Auto scroll speed</string>
<string name="sort_by_url">Sort by URL</string> <string name="sort_by_url">Sort by URL</string>
<string name="backup_summary">The local backup and WebDav backup at the same time</string> <string name="backup_summary">Backup the local and WebDav simultaneously</string>
<string name="restore_summary">Restore from WebDAV first, and Restore form local backups if failed</string> <string name="restore_summary">Restore from WebDAV first, and Restore form the local backup if failed</string>
<string name="import_old_summary">Select a legacy backup folder</string> <string name="import_old_summary">Select a legacy backup folder</string>
<string name="enabled">Enabled</string> <string name="enabled">Enabled</string>
<string name="disabled">Disabled</string> <string name="disabled">Disabled</string>
@ -711,16 +711,19 @@
<string name="starting_download">Starting download</string> <string name="starting_download">Starting download</string>
<string name="already_in_download">This book is already in Download list</string> <string name="already_in_download">This book is already in Download list</string>
<string name="click_to_open">click to open</string> <string name="click_to_open">click to open</string>
<string name="follow_public_account_summary">关注[开源阅读]点击广告支持我</string> <string name="follow_public_account_summary">Follow [开源阅读] to support me by clicking on ads</string>
<string name="weChat_appreciation_code">微信赞赏码</string> <string name="weChat_appreciation_code">WeChat Tipping Code</string>
<string name="alipay">支付宝</string> <string name="alipay">AliPay</string>
<string name="alipay_red_envelope_search_code">支付宝红包搜索码</string> <string name="alipay_red_envelope_search_code">AliPay red envelope search code</string>
<string name="alipay_red_envelope_copy">537954522 点击复制</string> <string name="alipay_red_envelope_copy">537954522 Click to copy</string>
<string name="alipay_red_envelope_qr_code">支付宝红包二维码</string> <string name="alipay_red_envelope_qr_code">AliPay red envelope QR code</string>
<string name="alipay_payment_qr_code">支付宝收款二维码</string> <string name="alipay_payment_qr_code">AliPay QR code</string>
<string name="qq_collection_qr_code">QQ收款二维码</string> <string name="qq_collection_qr_code">QQ Collection QR code</string>
<string name="contributors_summary">gedoor,Invinciblelee等,详情请在github中查看</string> <string name="contributors_summary">gedoor,Invinciblelee etc. Checking in github for details</string>
<string name="clear_cache_summary">清除已下载书籍和字体缓存</string> <string name="clear_cache_summary">Clear the cache of the downloaded books and fonts</string>
<string name="default_cover">默认封面</string> <string name="default_cover">Default cover</string>
<string name="restore_ignore">恢复忽略列表</string>
<string name="restore_ignore_summary">恢复时忽略一些内容不恢复,方便不同手机配置不同</string>
<string name="read_config">阅读界面设置</string>
</resources> </resources>

@ -39,6 +39,7 @@
</io.legado.app.ui.widget.prefs.PreferenceCategory> </io.legado.app.ui.widget.prefs.PreferenceCategory>
<io.legado.app.ui.widget.prefs.PreferenceCategory <io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="@string/backup_restore"
app:allowDividerBelow="false" app:allowDividerBelow="false"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:layout="@layout/view_preference_category"> app:layout="@layout/view_preference_category">
@ -61,6 +62,12 @@
android:summary="@string/restore_summary" android:summary="@string/restore_summary"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference
android:key="restoreIgnore"
android:title="@string/restore_ignore"
android:summary="@string/restore_ignore_summary"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference <io.legado.app.ui.widget.prefs.Preference
android:key="import_old" android:key="import_old"
android:title="@string/menu_import_old_version" android:title="@string/menu_import_old_version"

Loading…
Cancel
Save