Merge branches 'master' and 'master' of github.com:52fisher/legado

pull/123/head
fisher 5 years ago
commit b87359661e
  1. 20
      app/src/main/assets/updateLog.md
  2. 22
      app/src/main/java/io/legado/app/App.kt
  3. 2
      app/src/main/java/io/legado/app/constant/AppConst.kt
  4. 2
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  5. 58
      app/src/main/java/io/legado/app/help/BookHelp.kt
  6. 2
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  7. 50
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  8. 9
      app/src/main/java/io/legado/app/help/http/AjaxWebView.kt
  9. 4
      app/src/main/java/io/legado/app/help/http/CookieStore.kt
  10. 5
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  11. 15
      app/src/main/java/io/legado/app/help/storage/ImportOldData.kt
  12. 16
      app/src/main/java/io/legado/app/help/storage/OldRule.kt
  13. 1
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  14. 2
      app/src/main/java/io/legado/app/lib/theme/ATH.kt
  15. 6
      app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt
  16. 14
      app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt
  17. 4
      app/src/main/java/io/legado/app/lib/theme/ThemeStoreInterface.kt
  18. 1
      app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.kt
  19. 2
      app/src/main/java/io/legado/app/model/Rss.kt
  20. 8
      app/src/main/java/io/legado/app/model/WebBook.kt
  21. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  22. 18
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  23. 104
      app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
  24. 20
      app/src/main/java/io/legado/app/model/webBook/BookContent.kt
  25. 31
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  26. 2
      app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
  27. 2
      app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt
  28. 28
      app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
  29. 19
      app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt
  30. 32
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  31. 1
      app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt
  32. 4
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  33. 7
      app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt
  34. 15
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt
  35. 46
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt
  36. 9
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt
  37. 2
      app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt
  38. 23
      app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt
  39. 92
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
  40. 21
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt
  41. 111
      app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt
  42. 39
      app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt
  43. 10
      app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt
  44. 48
      app/src/main/java/io/legado/app/ui/download/DownloadActivity.kt
  45. 4
      app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt
  46. 191
      app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt
  47. 4
      app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt
  48. 2
      app/src/main/java/io/legado/app/ui/filechooser/adapter/PathAdapter.kt
  49. 2
      app/src/main/java/io/legado/app/ui/filechooser/utils/FilePickerIcon.java
  50. 16
      app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt
  51. 65
      app/src/main/java/io/legado/app/ui/login/SourceLogin.kt
  52. 11
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  53. 8
      app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
  54. 46
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
  55. 12
      app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt
  56. 98
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt
  57. 28
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt
  58. 15
      app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt
  59. 3
      app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt
  60. 45
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt
  61. 81
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
  62. 21
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
  63. 54
      app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt
  64. 5
      app/src/main/java/io/legado/app/ui/widget/SelectActionBar.kt
  65. 42
      app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt
  66. 27
      app/src/main/java/io/legado/app/utils/UriExtensions.kt
  67. 24
      app/src/main/java/io/legado/app/utils/ViewExtensions.kt
  68. 75
      app/src/main/res/drawable/image_welcome.xml
  69. 2
      app/src/main/res/layout/activity_main.xml
  70. 2
      app/src/main/res/layout/activity_rss_read.xml
  71. 6
      app/src/main/res/layout/activity_source_login.xml
  72. 72
      app/src/main/res/layout/activity_welcome.xml
  73. 105
      app/src/main/res/layout/dialog_bookshelf_config.xml
  74. 3
      app/src/main/res/layout/dialog_number_picker.xml
  75. 1
      app/src/main/res/layout/view_read_menu.xml
  76. 2
      app/src/main/res/menu/source_debug.xml
  77. 11
      app/src/main/res/menu/source_login.xml
  78. 20
      app/src/main/res/values/arrays.xml
  79. 2
      app/src/main/res/values/colors.xml
  80. 22
      app/src/main/res/values/strings.xml
  81. 6
      app/src/main/res/xml/pref_config_read.xml
  82. 16
      app/src/main/res/xml/pref_config_theme.xml

@ -1,11 +1,27 @@
## 更新日志 ## 更新日志
* 旧版数据导入教程: * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
* 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 请关注[开源阅读软件]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。
**2020/03/02**
* 添加书源登录
* 替换规则实时生效
* 页面最后一行计算是否能放下时不计算行距
**2020/03/01**
* 修复书源解析的一个bug
* 添加底部操作栏颜色配置
* 修复滚动点击翻页,修复滚动最后一页显示加载中
* 去除备份恢复默认路径
* 尝试修复部分手机一键导入书源报错
* 翻页还有些bug不用反馈了,我已经知道,会修复的
**2020/02/29** **2020/02/29**
* 添加书源一键导入 * 添加书源一键导入
* 修复主题模式跟随系统 * 修复主题模式跟随系统
* 修复书源校验 * 修复书源校验
* 添加书架排序
* 添加点击翻页开关
* 修复共用布局没有记住配置的bug
**2020/02/28** **2020/02/28**
* 解决阅读界面部分字体超出范围的问题 * 解决阅读界面部分字体超出范围的问题

@ -78,26 +78,24 @@ class App : Application() {
ThemeStore.editTheme(this) ThemeStore.editTheme(this)
.primaryColor( .primaryColor(
getPrefInt("colorPrimaryNight", getCompatColor(R.color.md_blue_grey_600)) getPrefInt("colorPrimaryNight", getCompatColor(R.color.md_blue_grey_600))
) ).accentColor(
.accentColor(
getPrefInt("colorAccentNight", getCompatColor(R.color.md_brown_800)) getPrefInt("colorAccentNight", getCompatColor(R.color.md_brown_800))
) ).backgroundColor(
.backgroundColor(
getPrefInt("colorBackgroundNight", getCompatColor(R.color.shine_color)) getPrefInt("colorBackgroundNight", getCompatColor(R.color.shine_color))
) ).bottomBackground(
.apply() getPrefInt("colorBottomBackgroundNight", getCompatColor(R.color.md_grey_850))
).apply()
} else { } else {
ThemeStore.editTheme(this) ThemeStore.editTheme(this)
.primaryColor( .primaryColor(
getPrefInt("colorPrimary", getCompatColor(R.color.md_indigo_800)) getPrefInt("colorPrimary", getCompatColor(R.color.md_indigo_800))
) ).accentColor(
.accentColor(
getPrefInt("colorAccent", getCompatColor(R.color.md_red_600)) getPrefInt("colorAccent", getCompatColor(R.color.md_red_600))
) ).backgroundColor(
.backgroundColor(
getPrefInt("colorBackground", getCompatColor(R.color.md_grey_100)) getPrefInt("colorBackground", getCompatColor(R.color.md_grey_100))
) ).bottomBackground(
.apply() getPrefInt("colorBottomBackground", getCompatColor(R.color.md_grey_200))
).apply()
} }
} }

@ -22,7 +22,7 @@ object AppConst {
const val UA_NAME = "User-Agent" const val UA_NAME = "User-Agent"
val userAgent: String by lazy { val userAgent: String by lazy {
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"
} }
val SCRIPT_ENGINE: ScriptEngine by lazy { val SCRIPT_ENGINE: ScriptEngine by lazy {

@ -5,6 +5,7 @@ object PreferKey {
const val themeMode = "themeMode" const val themeMode = "themeMode"
const val downloadPath = "downloadPath" const val downloadPath = "downloadPath"
const val hideStatusBar = "hideStatusBar" const val hideStatusBar = "hideStatusBar"
const val clickTurnPage = "clickTurnPage"
const val clickAllNext = "clickAllNext" const val clickAllNext = "clickAllNext"
const val hideNavigationBar = "hideNavigationBar" const val hideNavigationBar = "hideNavigationBar"
const val precisionSearch = "precisionSearch" const val precisionSearch = "precisionSearch"
@ -16,6 +17,7 @@ object PreferKey {
const val nextKey = "nextKeyCode" const val nextKey = "nextKeyCode"
const val showRss = "showRss" const val showRss = "showRss"
const val bookshelfLayout = "bookshelfLayout" const val bookshelfLayout = "bookshelfLayout"
const val bookshelfSort = "bookshelfSort"
const val recordLog = "recordLog" const val recordLog = "recordLog"
const val processText = "process_text" const val processText = "process_text"
const val cleanCache = "cleanCache" const val cleanCache = "cleanCache"

@ -237,14 +237,30 @@ object BookHelp {
private var bookName: String? = null private var bookName: String? = null
private var bookOrigin: String? = null private var bookOrigin: String? = null
private var replaceRules: List<ReplaceRule> = arrayListOf() private var replaceRules: List<ReplaceRule> = arrayListOf()
var bodyIndentCount = App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2)
set(value) {
field = value
App.INSTANCE.putPrefInt(PreferKey.bodyIndent, value)
bodyIndent = " ".repeat(value)
}
var bodyIndent = " ".repeat(bodyIndentCount)
@Synchronized
fun upReplaceRules(name: String? = null, origin: String? = null) {
if (name != null) {
if (bookName != name || bookOrigin != origin) {
replaceRules = if (origin.isNullOrEmpty()) {
App.db.replaceRuleDao().findEnabledByScope(name)
} else {
App.db.replaceRuleDao().findEnabledByScope(name, origin)
}
bookName = name
bookOrigin = origin
}
} else {
val o = bookOrigin
bookName?.let {
replaceRules = if (o.isNullOrEmpty()) {
App.db.replaceRuleDao().findEnabledByScope(it)
} else {
App.db.replaceRuleDao().findEnabledByScope(it, o)
}
}
}
}
fun disposeContent( fun disposeContent(
title: String, title: String,
@ -253,23 +269,17 @@ object BookHelp {
content: String, content: String,
enableReplace: Boolean enableReplace: Boolean
): String { ): String {
synchronized(this) {
if (enableReplace && (bookName != name || bookOrigin != origin)) {
replaceRules = if (origin.isNullOrEmpty()) {
App.db.replaceRuleDao().findEnabledByScope(name)
} else {
App.db.replaceRuleDao().findEnabledByScope(name, origin)
}
}
}
var c = content var c = content
for (item in replaceRules) { if (enableReplace) {
item.pattern.let { upReplaceRules(name, origin)
if (it.isNotEmpty()) { for (item in replaceRules) {
c = if (item.isRegex) { item.pattern.let {
c.replace(it.toRegex(), item.replacement) if (it.isNotEmpty()) {
} else { c = if (item.isRegex) {
c.replace(it, item.replacement) c.replace(it.toRegex(), item.replacement)
} else {
c.replace(it, item.replacement)
}
} }
} }
} }
@ -281,6 +291,6 @@ object BookHelp {
1 -> c = ZhConvertBootstrap.newInstance().toSimple(c) 1 -> c = ZhConvertBootstrap.newInstance().toSimple(c)
2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c) 2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c)
} }
return c.replace("\\s*\\n+\\s*".toRegex(), "\n$bodyIndent") return c.replace("\\s*\\n+\\s*".toRegex(), "\n${ReadBookConfig.bodyIndent}")
} }
} }

@ -17,7 +17,7 @@ interface JsExtensions {
fun ajax(urlStr: String): String? { fun ajax(urlStr: String): String? {
return try { return try {
val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, null, null) val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, null, null)
val call = analyzeUrl.getResponse() val call = analyzeUrl.getResponse(urlStr)
val response = call.execute() val response = call.execute()
response.body() response.body()
} catch (e: Exception) { } catch (e: Exception) {

@ -26,23 +26,6 @@ object ReadBookConfig {
} }
val durConfig get() = getConfig(styleSelect) val durConfig get() = getConfig(styleSelect)
private val shareConfig get() = getConfig(5) private val shareConfig get() = getConfig(5)
var styleSelect = App.INSTANCE.getPrefInt(PreferKey.readStyleSelect)
set(value) {
field = value
App.INSTANCE.putPrefInt(PreferKey.readStyleSelect, value)
}
var shareLayout = App.INSTANCE.getPrefBoolean(PreferKey.shareLayout)
set(value) {
field = value
App.INSTANCE.putPrefBoolean(PreferKey.shareLayout, value)
}
var pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim)
set(value) {
field = value
isScroll = value == 3
App.INSTANCE.putPrefInt(PreferKey.pageAnim, value)
}
var isScroll = pageAnim == 3
var bg: Drawable? = null var bg: Drawable? = null
init { init {
@ -112,6 +95,39 @@ object ReadBookConfig {
} }
//配置写入读取 //配置写入读取
var styleSelect = App.INSTANCE.getPrefInt(PreferKey.readStyleSelect)
set(value) {
field = value
if (App.INSTANCE.getPrefInt(PreferKey.readStyleSelect) != value) {
App.INSTANCE.putPrefInt(PreferKey.readStyleSelect, value)
}
}
var shareLayout = App.INSTANCE.getPrefBoolean(PreferKey.shareLayout)
set(value) {
field = value
if (App.INSTANCE.getPrefBoolean(PreferKey.shareLayout) != value) {
App.INSTANCE.putPrefBoolean(PreferKey.shareLayout, value)
}
}
var pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim)
set(value) {
field = value
isScroll = value == 3
if (App.INSTANCE.getPrefInt(PreferKey.pageAnim) != value) {
App.INSTANCE.putPrefInt(PreferKey.pageAnim, value)
}
}
var isScroll = pageAnim == 3
val clickTurnPage get() = App.INSTANCE.getPrefBoolean(PreferKey.clickTurnPage, true)
var bodyIndentCount = App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2)
set(value) {
field = value
bodyIndent = " ".repeat(value)
if (App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2) != value) {
App.INSTANCE.putPrefInt(PreferKey.bodyIndent, value)
}
}
var bodyIndent = " ".repeat(bodyIndentCount)
var hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar) var hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar)
var hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar) var hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar)
var textBold: Boolean var textBold: Boolean

@ -1,7 +1,6 @@
package io.legado.app.help.http package io.legado.app.help.http
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.net.http.SslError
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
@ -167,10 +166,6 @@ class AjaxWebView {
).sendToTarget() ).sendToTarget()
} }
} }
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
handler.proceed()
}
} }
private class EvalJsRunnable( private class EvalJsRunnable(
@ -242,10 +237,6 @@ class AjaxWebView {
} }
} }
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
handler.proceed()
}
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
params.setCookie(url) params.setCookie(url)
if (params.hasJavaScript()) { if (params.hasJavaScript()) {

@ -3,15 +3,15 @@ package io.legado.app.help.http
import android.text.TextUtils import android.text.TextUtils
import io.legado.app.App import io.legado.app.App
import io.legado.app.data.entities.Cookie import io.legado.app.data.entities.Cookie
import io.legado.app.help.coroutine.Coroutine
object CookieStore { object CookieStore {
fun setCookie(url: String, cookie: String?) { fun setCookie(url: String, cookie: String?) {
try { Coroutine.async {
val cookieBean = Cookie(url, cookie ?: "") val cookieBean = Cookie(url, cookie ?: "")
App.db.cookieDao().insert(cookieBean) App.db.cookieDao().insert(cookieBean)
} catch (ignore: Exception) {
} }
} }

@ -101,9 +101,8 @@ object Backup {
val file = File(backupPath + File.separator + fileName) val file = File(backupPath + File.separator + fileName)
if (file.exists()) { if (file.exists()) {
treeDoc.findFile(fileName)?.delete() treeDoc.findFile(fileName)?.delete()
treeDoc.createFile("", fileName)?.let { treeDoc.createFile("", fileName)
DocumentUtils.writeBytes(context, file.readBytes(), it.uri) ?.writeBytes(context, file.readBytes())
}
} }
} }
} }

@ -17,15 +17,12 @@ import org.jetbrains.anko.toast
import java.io.File import java.io.File
object ImportOldData { object ImportOldData {
val yueDuPath by lazy {
FileUtils.getSdCardPath() + File.separator + "YueDu"
}
fun import(context: Context) { fun import(context: Context, file: File) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
try {// 导入书架 try {// 导入书架
val shelfFile = val shelfFile =
FileUtils.createFileIfNotExist(yueDuPath + File.separator + "myBookShelf.json") FileUtils.createFileIfNotExist(file, "myBookShelf.json")
val json = shelfFile.readText() val json = shelfFile.readText()
val importCount = importOldBookshelf(json) val importCount = importOldBookshelf(json)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -39,7 +36,7 @@ object ImportOldData {
try {// Book source try {// Book source
val sourceFile = val sourceFile =
FileUtils.createFileIfNotExist(yueDuPath + File.separator + "myBookSource.json") FileUtils.createFileIfNotExist(file, "myBookSource.json")
val json = sourceFile.readText() val json = sourceFile.readText()
val importCount = importOldSource(json) val importCount = importOldSource(json)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -53,7 +50,7 @@ object ImportOldData {
try {// Replace rules try {// Replace rules
val ruleFile = val ruleFile =
FileUtils.createFileIfNotExist(yueDuPath + File.separator + "myBookReplaceRule.json") FileUtils.createFileIfNotExist(file, "myBookReplaceRule.json")
val json = ruleFile.readText() val json = ruleFile.readText()
val importCount = importOldReplaceRule(json) val importCount = importOldReplaceRule(json)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -115,13 +112,13 @@ object ImportOldData {
} }
} }
fun importOldBookshelf(json: String): Int { private fun importOldBookshelf(json: String): Int {
val books = OldBook.toNewBook(json) val books = OldBook.toNewBook(json)
App.db.bookDao().insert(*books.toTypedArray()) App.db.bookDao().insert(*books.toTypedArray())
return books.size return books.size
} }
fun importOldSource(json: String): Int { private fun importOldSource(json: String): Int {
val bookSources = mutableListOf<BookSource>() val bookSources = mutableListOf<BookSource>()
val items: List<Map<String, Any>> = Restore.jsonPath.parse(json).read("$") val items: List<Map<String, Any>> = Restore.jsonPath.parse(json).read("$")
for (item in items) { for (item in items) {

@ -100,15 +100,22 @@ object OldRule {
if (oldRule.isNullOrBlank()) return null if (oldRule.isNullOrBlank()) return null
var newRule = oldRule var newRule = oldRule
var reverse = false var reverse = false
var allinone = false
if (oldRule.startsWith("-")) { if (oldRule.startsWith("-")) {
reverse = true reverse = true
newRule = oldRule.substring(1) newRule = oldRule.substring(1)
} }
if (newRule.startsWith("+")) {
allinone = true
newRule = newRule.substring(1)
}
if (!newRule.startsWith("@CSS:", true) && if (!newRule.startsWith("@CSS:", true) &&
!newRule.startsWith("@XPath:", true) && !newRule.startsWith("@XPath:", true) &&
!newRule.startsWith("//") && !newRule.startsWith("//") &&
!newRule.startsWith("##") && !newRule.startsWith("##") &&
!newRule.startsWith(":") !newRule.startsWith(":") &&
!newRule.contains("@js:",true) &&
!newRule.contains("<js>",true)
) { ) {
if (newRule.contains("#") && !newRule.contains("##")) { if (newRule.contains("#") && !newRule.contains("##")) {
newRule = oldRule.replace("#", "##") newRule = oldRule.replace("#", "##")
@ -134,8 +141,11 @@ object OldRule {
newRule = newRule.replace("&", "&&") newRule = newRule.replace("&", "&&")
} }
} }
if (allinone) {
newRule = "+" + newRule
}
if (reverse) { if (reverse) {
newRule += "-" newRule = "-" + newRule
} }
return newRule return newRule
} }
@ -145,7 +155,7 @@ object OldRule {
if (!oldUrls.contains("\n") && !oldUrls.contains("&&")) if (!oldUrls.contains("\n") && !oldUrls.contains("&&"))
return toNewUrl(oldUrls) return toNewUrl(oldUrls)
val urls = oldUrls.split("(&&|\n)+".toRegex()) val urls = oldUrls.split("(&&|\r?\n)+".toRegex())
return urls.map { return urls.map {
toNewUrl(it)?.replace("\n\\s*".toRegex(), "") toNewUrl(it)?.replace("\n\\s*".toRegex(), "")
}.joinToString("\n") }.joinToString("\n")

@ -114,6 +114,7 @@ object Restore {
pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim) pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim)
hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar) hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar)
hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar) hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar)
bodyIndentCount = App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2)
} }
ChapterProvider.upStyle() ChapterProvider.upStyle()
ReadBook.loadContent() ReadBook.loadContent()

@ -205,7 +205,7 @@ object ATH {
.setSelectedColor(ThemeStore.accentColor(bottom_navigation_view.context)).create() .setSelectedColor(ThemeStore.accentColor(bottom_navigation_view.context)).create()
itemIconTintList = colorStateList itemIconTintList = colorStateList
itemTextColor = colorStateList itemTextColor = colorStateList
itemBackgroundResource = R.color.background_menu setBackgroundColor(ThemeStore.bottomBackground(context))
} }
} }

@ -54,6 +54,9 @@ val Context.accentColor: Int
val Context.backgroundColor: Int val Context.backgroundColor: Int
get() = ThemeStore.backgroundColor(this) get() = ThemeStore.backgroundColor(this)
val Context.bottomBackground: Int
get() = ThemeStore.bottomBackground(this)
val Context.primaryTextColor: Int val Context.primaryTextColor: Int
get() = getPrimaryTextColor(isDarkTheme) get() = getPrimaryTextColor(isDarkTheme)
@ -78,6 +81,9 @@ val Fragment.accentColor: Int
val Fragment.backgroundColor: Int val Fragment.backgroundColor: Int
get() = ThemeStore.backgroundColor(requireContext()) get() = ThemeStore.backgroundColor(requireContext())
val Fragment.bottomBackground: Int
get() = ThemeStore.bottomBackground(requireContext())
val Fragment.primaryTextColor: Int val Fragment.primaryTextColor: Int
get() = requireContext().getPrimaryTextColor(isDarkTheme) get() = requireContext().getPrimaryTextColor(isDarkTheme)

@ -147,6 +147,11 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
return this return this
} }
override fun bottomBackground(color: Int): ThemeStore {
mEditor.putInt(ThemeStorePrefKeys.KEY_BOTTOM_BACKGROUND, color)
return this
}
override fun coloredStatusBar(colored: Boolean): ThemeStore { override fun coloredStatusBar(colored: Boolean): ThemeStore {
mEditor.putBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, colored) mEditor.putBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, colored)
return this return this
@ -279,6 +284,15 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
) )
} }
@CheckResult
@ColorInt
fun bottomBackground(context: Context): Int {
return prefs(context).getInt(
ThemeStorePrefKeys.KEY_BOTTOM_BACKGROUND,
ATHUtils.resolveColor(context, android.R.attr.colorBackground)
)
}
@CheckResult @CheckResult
fun coloredStatusBar(context: Context): Boolean { fun coloredStatusBar(context: Context): Boolean {
return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, true) return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, true)

@ -78,8 +78,12 @@ internal interface ThemeStoreInterface {
fun textColorSecondaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore fun textColorSecondaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore
// Background
fun backgroundColor(@ColorInt color: Int): ThemeStore fun backgroundColor(@ColorInt color: Int): ThemeStore
fun bottomBackground(@ColorInt color: Int): ThemeStore
// Toggle configurations // Toggle configurations
fun coloredStatusBar(colored: Boolean): ThemeStore fun coloredStatusBar(colored: Boolean): ThemeStore

@ -22,6 +22,7 @@ object ThemeStorePrefKeys {
const val KEY_TEXT_COLOR_SECONDARY_INVERSE = "text_color_secondary_inverse" const val KEY_TEXT_COLOR_SECONDARY_INVERSE = "text_color_secondary_inverse"
const val KEY_BACKGROUND_COLOR = "backgroundColor" const val KEY_BACKGROUND_COLOR = "backgroundColor"
const val KEY_BOTTOM_BACKGROUND = "bottomBackground"
const val KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar" const val KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar"
const val KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar" const val KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar"

@ -35,7 +35,7 @@ object Rss {
): Coroutine<String> { ): Coroutine<String> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
val body = AnalyzeUrl(rssArticle.link, baseUrl = rssArticle.origin) val body = AnalyzeUrl(rssArticle.link, baseUrl = rssArticle.origin)
.getResponseAwait() .getResponseAwait(rssArticle.origin)
.body .body
val analyzeRule = AnalyzeRule() val analyzeRule = AnalyzeRule()
analyzeRule.setContent( analyzeRule.setContent(

@ -37,7 +37,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl, baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
) )
val res = analyzeUrl.getResponseAwait() val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl)
BookList.analyzeBookList( BookList.analyzeBookList(
res.body, res.body,
bookSource, bookSource,
@ -65,7 +65,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl, baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
) )
val res = analyzeUrl.getResponseAwait() val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl)
BookList.analyzeBookList( BookList.analyzeBookList(
res.body, res.body,
bookSource, bookSource,
@ -96,7 +96,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl, baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
) )
analyzeUrl.getResponseAwait().body analyzeUrl.getResponseAwait(bookSource.bookSourceUrl).body
} }
BookInfo.analyzeBookInfo(book, body, bookSource, book.bookUrl) BookInfo.analyzeBookInfo(book, body, bookSource, book.bookUrl)
book book
@ -122,7 +122,7 @@ class WebBook(val bookSource: BookSource) {
ruleUrl = book.tocUrl, ruleUrl = book.tocUrl,
baseUrl = book.bookUrl, baseUrl = book.bookUrl,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
).getResponseAwait().body ).getResponseAwait(bookSource.bookSourceUrl).body
} }
BookChapterList.analyzeChapterList(this, book, body, bookSource, book.tocUrl) BookChapterList.analyzeChapterList(this, book, body, bookSource, book.tocUrl)
} }

@ -610,7 +610,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
override fun ajax(urlStr: String): String? { override fun ajax(urlStr: String): String? {
return try { return try {
val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, baseUrl, book) val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, baseUrl, book)
val call = analyzeUrl.getResponse() val call = analyzeUrl.getResponse(urlStr)
val response = call.execute() val response = call.execute()
response.body() response.body()
} catch (e: Exception) { } catch (e: Exception) {

@ -8,10 +8,7 @@ import io.legado.app.constant.Pattern.EXP_PATTERN
import io.legado.app.constant.Pattern.JS_PATTERN import io.legado.app.constant.Pattern.JS_PATTERN
import io.legado.app.data.entities.BaseBook import io.legado.app.data.entities.BaseBook
import io.legado.app.help.JsExtensions import io.legado.app.help.JsExtensions
import io.legado.app.help.http.AjaxWebView import io.legado.app.help.http.*
import io.legado.app.help.http.HttpHelper
import io.legado.app.help.http.RequestMethod
import io.legado.app.help.http.Res
import io.legado.app.help.http.api.HttpGetApi import io.legado.app.help.http.api.HttpGetApi
import io.legado.app.help.http.api.HttpPostApi import io.legado.app.help.http.api.HttpPostApi
import io.legado.app.utils.* import io.legado.app.utils.*
@ -243,7 +240,11 @@ class AnalyzeUrl(
} }
@Throws(Exception::class) @Throws(Exception::class)
fun getResponse(): Call<String> { fun getResponse(tag: String): Call<String> {
val cookie = CookieStore.getCookie(tag)
if (cookie.isNotEmpty()) {
headerMap["Cookie"] = cookie
}
return when { return when {
method == RequestMethod.POST -> { method == RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) { if (fieldMap.isNotEmpty()) {
@ -267,7 +268,7 @@ class AnalyzeUrl(
@Throws(Exception::class) @Throws(Exception::class)
suspend fun getResponseAwait( suspend fun getResponseAwait(
tag: String? = null, tag: String,
jsStr: String? = null, jsStr: String? = null,
sourceRegex: String? = null sourceRegex: String? = null
): Res { ): Res {
@ -278,8 +279,13 @@ class AnalyzeUrl(
params.javaScript = jsStr params.javaScript = jsStr
params.sourceRegex = sourceRegex params.sourceRegex = sourceRegex
params.postData = bodyTxt?.toByteArray() params.postData = bodyTxt?.toByteArray()
params.tag = tag
return HttpHelper.ajax(params) return HttpHelper.ajax(params)
} }
val cookie = CookieStore.getCookie(tag)
if (cookie.isNotEmpty()) {
headerMap["Cookie"] = cookie
}
val res = when { val res = when {
method == RequestMethod.POST -> { method == RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) { if (fieldMap.isNotEmpty()) {

@ -31,7 +31,7 @@ object BookChapterList {
App.INSTANCE.getString(R.string.error_get_web_content, baseUrl) App.INSTANCE.getString(R.string.error_get_web_content, baseUrl)
) )
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}") Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}")
val analyzeRule = AnalyzeRule(book)
val tocRule = bookSource.getTocRule() val tocRule = bookSource.getTocRule()
val nextUrlList = arrayListOf(baseUrl) val nextUrlList = arrayListOf(baseUrl)
var reverse = false var reverse = false
@ -45,8 +45,7 @@ object BookChapterList {
} }
var chapterData = var chapterData =
analyzeChapterList( analyzeChapterList(
analyzeRule.setContent(body, baseUrl), book, baseUrl, body, tocRule, listRule, bookSource, log = true
book.bookUrl, baseUrl, tocRule, listRule, bookSource, log = true
) )
chapterData.chapterList?.let { chapterData.chapterList?.let {
chapterList.addAll(it) chapterList.addAll(it)
@ -64,11 +63,10 @@ object BookChapterList {
ruleUrl = nextUrl, ruleUrl = nextUrl,
book = book, book = book,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
).getResponseAwait() ).getResponseAwait(bookSource.bookSourceUrl)
.body?.let { nextBody -> .body?.let { nextBody ->
chapterData = analyzeChapterList( chapterData = analyzeChapterList(
analyzeRule.setContent(nextBody, nextUrl), book, nextUrl, nextBody, tocRule, listRule, bookSource
book.bookUrl, nextUrl, tocRule, listRule, bookSource
) )
nextUrl = if (chapterData.nextUrl.isNotEmpty()) { nextUrl = if (chapterData.nextUrl.isNotEmpty()) {
chapterData.nextUrl[0] chapterData.nextUrl[0]
@ -95,35 +93,20 @@ object BookChapterList {
} }
Debug.log(bookSource.bookSourceUrl, "◇目录总页数:${nextUrlList.size}") Debug.log(bookSource.bookSourceUrl, "◇目录总页数:${nextUrlList.size}")
for (item in chapterDataList) { for (item in chapterDataList) {
Coroutine.async(scope = coroutineScope) { downloadToc(
val nextBody = AnalyzeUrl( coroutineScope,
ruleUrl = item.nextUrl, item,
book = book, book,
headerMapF = bookSource.getHeaderMap() bookSource,
).getResponseAwait().body tocRule,
val nextChapterData = analyzeChapterList( listRule,
analyzeRule.setContent(nextBody, item.nextUrl), chapterList,
book.bookUrl, item.nextUrl, tocRule, listRule, bookSource, chapterDataList,
false {
) block.resume(finish(book, chapterList, reverse))
synchronized(chapterDataList) { }, {
val isFinished = addChapterListIsFinish( block.cancel(it)
chapterDataList, })
item,
nextChapterData.chapterList
)
if (isFinished) {
chapterDataList.forEach { item ->
item.chapterList?.let {
chapterList.addAll(it)
}
}
block.resume(finish(book, chapterList, reverse))
}
}
}.onError {
block.resumeWithException(it)
}
} }
} }
} }
@ -132,6 +115,49 @@ object BookChapterList {
} }
} }
private fun downloadToc(
coroutineScope: CoroutineScope,
chapterData: ChapterData<String>,
book: Book,
bookSource: BookSource,
tocRule: TocRule,
listRule: String,
chapterList: ArrayList<BookChapter>,
chapterDataList: ArrayList<ChapterData<String>>,
onFinish: () -> Unit,
onError: (e: Throwable) -> Unit
) {
Coroutine.async(scope = coroutineScope) {
val nextBody = AnalyzeUrl(
ruleUrl = chapterData.nextUrl,
book = book,
headerMapF = bookSource.getHeaderMap()
).getResponseAwait(bookSource.bookSourceUrl).body
?: throw Exception("${chapterData.nextUrl}, 下载失败")
val nextChapterData = analyzeChapterList(
book, chapterData.nextUrl, nextBody, tocRule, listRule, bookSource,
false
)
synchronized(chapterDataList) {
val isFinished = addChapterListIsFinish(
chapterDataList,
chapterData,
nextChapterData.chapterList
)
if (isFinished) {
chapterDataList.forEach { item ->
item.chapterList?.let {
chapterList.addAll(it)
}
}
onFinish()
}
}
}.onError {
onError(it)
}
}
private fun addChapterListIsFinish( private fun addChapterListIsFinish(
chapterDataList: ArrayList<ChapterData<String>>, chapterDataList: ArrayList<ChapterData<String>>,
chapterData: ChapterData<String>, chapterData: ChapterData<String>,
@ -173,15 +199,17 @@ object BookChapterList {
} }
private fun analyzeChapterList( private fun analyzeChapterList(
analyzeRule: AnalyzeRule, book: Book,
bookUrl: String,
baseUrl: String, baseUrl: String,
body: String,
tocRule: TocRule, tocRule: TocRule,
listRule: String, listRule: String,
bookSource: BookSource, bookSource: BookSource,
getNextUrl: Boolean = true, getNextUrl: Boolean = true,
log: Boolean = false log: Boolean = false
): ChapterData<List<String>> { ): ChapterData<List<String>> {
val analyzeRule = AnalyzeRule(book)
analyzeRule.setContent(body, baseUrl)
val chapterList = arrayListOf<BookChapter>() val chapterList = arrayListOf<BookChapter>()
val nextUrlList = arrayListOf<String>() val nextUrlList = arrayListOf<String>()
val nextTocRule = tocRule.nextTocUrl val nextTocRule = tocRule.nextTocUrl
@ -212,7 +240,7 @@ object BookChapterList {
var isVip: String? var isVip: String?
for (item in elements) { for (item in elements) {
analyzeRule.setContent(item) analyzeRule.setContent(item)
val bookChapter = BookChapter(bookUrl = bookUrl) val bookChapter = BookChapter(bookUrl = book.bookUrl)
analyzeRule.chapter = bookChapter analyzeRule.chapter = bookChapter
bookChapter.title = analyzeRule.getString(nameRule) bookChapter.title = analyzeRule.getString(nameRule)
bookChapter.url = analyzeRule.getString(urlRule, true) bookChapter.url = analyzeRule.getString(urlRule, true)

@ -33,13 +33,11 @@ object BookContent {
) )
) )
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}") Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}")
val analyzeRule = AnalyzeRule(book)
val content = StringBuilder() val content = StringBuilder()
val nextUrlList = arrayListOf(baseUrl) val nextUrlList = arrayListOf(baseUrl)
val contentRule = bookSource.getContentRule() val contentRule = bookSource.getContentRule()
var contentData = analyzeContent( var contentData = analyzeContent(
analyzeRule.setContent(body, baseUrl), book, baseUrl, body, contentRule, bookChapter, bookSource
contentRule, bookChapter, bookSource
) )
content.append(contentData.content.replace(bookChapter.title, "")) content.append(contentData.content.replace(bookChapter.title, ""))
if (contentData.nextUrl.size == 1) { if (contentData.nextUrl.size == 1) {
@ -58,12 +56,11 @@ object BookContent {
ruleUrl = nextUrl, ruleUrl = nextUrl,
book = book, book = book,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
).getResponseAwait() ).getResponseAwait(bookSource.bookSourceUrl)
.body?.let { nextBody -> .body?.let { nextBody ->
contentData = contentData =
analyzeContent( analyzeContent(
analyzeRule.setContent(nextBody, nextUrl), book, nextUrl, nextBody, contentRule, bookChapter, bookSource, false
contentRule, bookChapter, bookSource, false
) )
nextUrl = nextUrl =
if (contentData.nextUrl.isNotEmpty()) contentData.nextUrl[0] else "" if (contentData.nextUrl.isNotEmpty()) contentData.nextUrl[0] else ""
@ -83,12 +80,11 @@ object BookContent {
ruleUrl = item.nextUrl, ruleUrl = item.nextUrl,
book = book, book = book,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
).getResponseAwait() ).getResponseAwait(bookSource.bookSourceUrl)
.body?.let { .body?.let {
contentData = contentData =
analyzeContent( analyzeContent(
analyzeRule.setContent(it, item.nextUrl), book, item.nextUrl, it, contentRule, bookChapter, bookSource, false
contentRule, bookChapter, bookSource, false
) )
item.content = contentData.content item.content = contentData.content
} }
@ -108,12 +104,16 @@ object BookContent {
@Throws(Exception::class) @Throws(Exception::class)
private fun analyzeContent( private fun analyzeContent(
analyzeRule: AnalyzeRule, book: Book,
baseUrl: String,
body: String,
contentRule: ContentRule, contentRule: ContentRule,
chapter: BookChapter, chapter: BookChapter,
bookSource: BookSource, bookSource: BookSource,
printLog: Boolean = true printLog: Boolean = true
): ContentData<List<String>> { ): ContentData<List<String>> {
val analyzeRule = AnalyzeRule(book)
analyzeRule.setContent(body, baseUrl)
val nextUrlList = arrayListOf<String>() val nextUrlList = arrayListOf<String>()
analyzeRule.chapter = chapter analyzeRule.chapter = chapter
val nextUrlRule = contentRule.nextContentUrl val nextUrlRule = contentRule.nextContentUrl

@ -20,7 +20,9 @@ import io.legado.app.constant.PreferKey
import io.legado.app.constant.Status import io.legado.app.constant.Status
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.dialogs.okButton
@ -40,6 +42,7 @@ import io.legado.app.ui.book.read.page.delegate.PageDelegate
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.changesource.ChangeSourceDialog import io.legado.app.ui.changesource.ChangeSourceDialog
import io.legado.app.ui.chapterlist.ChapterListActivity import io.legado.app.ui.chapterlist.ChapterListActivity
import io.legado.app.ui.login.SourceLogin
import io.legado.app.ui.replacerule.ReplaceRuleActivity import io.legado.app.ui.replacerule.ReplaceRuleActivity
import io.legado.app.ui.replacerule.edit.ReplaceEditDialog import io.legado.app.ui.replacerule.edit.ReplaceEditDialog
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
@ -63,6 +66,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
ChangeSourceDialog.CallBack, ChangeSourceDialog.CallBack,
ReadBook.CallBack, ReadBook.CallBack,
TocRegexDialog.CallBack, TocRegexDialog.CallBack,
ReplaceEditDialog.CallBack,
ColorPickerDialogListener { ColorPickerDialogListener {
private val requestCodeChapterList = 568 private val requestCodeChapterList = 568
private val requestCodeEditSource = 111 private val requestCodeEditSource = 111
@ -219,6 +223,12 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
supportFragmentManager, supportFragmentManager,
ReadBook.book?.tocUrl ReadBook.book?.tocUrl
) )
R.id.menu_login -> ReadBook.webBook?.bookSource?.let {
startActivity<SourceLogin>(
Pair("sourceUrl", it.bookSourceUrl),
Pair("loginUrl", it.loginUrl)
)
}
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }
@ -544,14 +554,33 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
} }
} }
/**
* 替换规则变化
*/
override fun onReplaceRuleSave() {
Coroutine.async {
BookHelp.upReplaceRules()
ReadBook.loadContent()
}
}
/**
* 显示阅读样式配置
*/
override fun showReadStyle() { override fun showReadStyle() {
ReadStyleDialog().show(supportFragmentManager, "readStyle") ReadStyleDialog().show(supportFragmentManager, "readStyle")
} }
/**
* 显示更多设置
*/
override fun showMoreSetting() { override fun showMoreSetting() {
MoreConfigDialog().show(supportFragmentManager, "moreConfig") MoreConfigDialog().show(supportFragmentManager, "moreConfig")
} }
/**
* 更新状态栏,导航栏
*/
override fun upSystemUiVisibility() { override fun upSystemUiVisibility() {
Help.upSystemUiVisibility(this, !read_menu.isVisible) Help.upSystemUiVisibility(this, !read_menu.isVisible)
} }
@ -610,7 +639,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
viewModel.openChapter(index) viewModel.openChapter(index)
} }
} }
requestCodeReplace -> ReadBook.loadContent() requestCodeReplace -> onReplaceRuleSave()
} }
} }
} }

@ -12,6 +12,7 @@ import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
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.lib.theme.buttonDisabledColor import io.legado.app.lib.theme.buttonDisabledColor
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
@ -48,6 +49,7 @@ class ReadMenu : FrameLayout {
fabNightTheme.setImageResource(R.drawable.ic_brightness) fabNightTheme.setImageResource(R.drawable.ic_brightness)
} }
initAnimation() initAnimation()
ATH.applyBackgroundTint(fl_bottom_bg)
vw_bg.onClick { } vw_bg.onClick { }
vwNavigationBar.onClick { } vwNavigationBar.onClick { }
seek_brightness.progress = context.getPrefInt("brightness", 100) seek_brightness.progress = context.getPrefInt("brightness", 100)

@ -95,6 +95,7 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
onMenuItemSelected(it) onMenuItemSelected(it)
} }
} }
callBack.onMenuActionFinally()
} }
} }
} }
@ -121,7 +122,6 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
} }
} }
} }
callBack.onMenuActionFinally()
} }
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)

@ -11,7 +11,6 @@ import androidx.fragment.app.DialogFragment
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.BookHelp
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.dialogs.selector import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
@ -19,6 +18,7 @@ import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.book.read.Help import io.legado.app.ui.book.read.Help
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.widget.font.FontSelectDialog import io.legado.app.ui.widget.font.FontSelectDialog
import io.legado.app.utils.getIndexById
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import io.legado.app.utils.putPrefString import io.legado.app.utils.putPrefString
@ -30,6 +30,8 @@ import org.jetbrains.anko.sdk27.listeners.onLongClick
class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
val callBack get() = activity as? ReadBookActivity
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val dm = DisplayMetrics() val dm = DisplayMetrics()
@ -115,16 +117,13 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
title = getString(R.string.text_indent), title = getString(R.string.text_indent),
items = resources.getStringArray(R.array.indent).toList() items = resources.getStringArray(R.array.indent).toList()
) { _, index -> ) { _, index ->
BookHelp.bodyIndentCount = index ReadBookConfig.bodyIndentCount = index
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
} }
tv_padding.onClick { tv_padding.onClick {
val activity = activity
dismiss() dismiss()
if (activity is ReadBookActivity) { callBack?.showPaddingConfig()
activity.showPaddingConfig()
}
} }
dsb_text_size.onChanged = { dsb_text_size.onChanged = {
ReadBookConfig.textSize = it + 5 ReadBookConfig.textSize = it + 5
@ -143,15 +142,9 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
rg_page_anim.onCheckedChange { _, checkedId -> rg_page_anim.onCheckedChange { _, checkedId ->
for (i in 0 until rg_page_anim.childCount) { rg_page_anim.getIndexById(checkedId).let {
if (checkedId == rg_page_anim[i].id) { ReadBookConfig.pageAnim = it
ReadBookConfig.pageAnim = i callBack?.page_view?.upPageAnim()
val activity = activity
if (activity is ReadBookActivity) {
activity.page_view.upPageAnim()
}
break
}
} }
} }
cb_share_layout.onCheckedChangeListener = { checkBox, isChecked -> cb_share_layout.onCheckedChangeListener = { checkBox, isChecked ->
@ -186,10 +179,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
private fun showBgTextConfig(index: Int): Boolean { private fun showBgTextConfig(index: Int): Boolean {
dismiss() dismiss()
changeBg(index) changeBg(index)
val activity = activity callBack?.showBgTextConfig()
if (activity is ReadBookActivity) {
activity.showBgTextConfig()
}
return true return true
} }

@ -9,7 +9,6 @@ import io.legado.app.App
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.entities.TextLine import io.legado.app.ui.book.read.page.entities.TextLine
@ -34,7 +33,6 @@ object ChapterProvider {
var typeface: Typeface = Typeface.SANS_SERIF var typeface: Typeface = Typeface.SANS_SERIF
var titlePaint = TextPaint() var titlePaint = TextPaint()
var contentPaint = TextPaint() var contentPaint = TextPaint()
private var bodyIndent = BookHelp.bodyIndent
init { init {
upStyle() upStyle()
@ -122,10 +120,12 @@ object ChapterProvider {
) )
for (lineIndex in 0 until layout.lineCount) { for (lineIndex in 0 until layout.lineCount) {
textPages.last().height = durY textPages.last().height = durY
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
val textLine = TextLine(isTitle = true) val textLine = TextLine(isTitle = true)
if (durY < visibleHeight) { if (durY + layout.getLineBaseline(lineIndex) - layout.getLineTop(lineIndex)
+ contentPaint.fontMetrics.descent < visibleHeight
) {
textPages.last().textLines.add(textLine) textPages.last().textLines.add(textLine)
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
} else { } else {
textPages.last().text = stringBuilder.toString() textPages.last().text = stringBuilder.toString()
stringBuilder.clear() stringBuilder.clear()
@ -140,7 +140,7 @@ object ChapterProvider {
(layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat() (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat()
textLine.lineBase = (paddingTop + durY - textLine.lineBase = (paddingTop + durY -
(layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat() (layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat()
textLine.lineBottom = textLine.lineBase + titlePaint.fontMetrics.descent textLine.lineBottom = textLine.lineBase + contentPaint.fontMetrics.descent
val words = val words =
title.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) title.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex))
stringBuilder.append(words) stringBuilder.append(words)
@ -178,10 +178,12 @@ object ChapterProvider {
) )
for (lineIndex in 0 until layout.lineCount) { for (lineIndex in 0 until layout.lineCount) {
textPages.last().height = durY textPages.last().height = durY
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
val textLine = TextLine() val textLine = TextLine()
if (durY < visibleHeight) { if (durY + layout.getLineBaseline(lineIndex) - layout.getLineTop(lineIndex)
+ contentPaint.fontMetrics.descent < visibleHeight
) {
textPages.last().textLines.add(textLine) textPages.last().textLines.add(textLine)
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
} else { } else {
textPages.last().text = stringBuilder.toString() textPages.last().text = stringBuilder.toString()
stringBuilder.clear() stringBuilder.clear()
@ -227,6 +229,7 @@ object ChapterProvider {
desiredWidth: Float desiredWidth: Float
) { ) {
var x = 0f var x = 0f
val bodyIndent = ReadBookConfig.bodyIndent
val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length
for (i in 0..bodyIndent.lastIndex) { for (i in 0..bodyIndent.lastIndex) {
val x1 = x + icw val x1 = x + icw
@ -348,8 +351,6 @@ object ChapterProvider {
titlePaint.textSize = (ReadBookConfig.textSize + 2).dp.toFloat() titlePaint.textSize = (ReadBookConfig.textSize + 2).dp.toFloat()
contentPaint.textSize = ReadBookConfig.textSize.dp.toFloat() contentPaint.textSize = ReadBookConfig.textSize.dp.toFloat()
bodyIndent = BookHelp.bodyIndent
upSize() upSize()
} }

@ -92,6 +92,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} }
if (!ReadBookConfig.isScroll) return if (!ReadBookConfig.isScroll) return
//滚动翻页 //滚动翻页
if (!pageFactory.hasNext()) return
val nextPage = relativePage(1) val nextPage = relativePage(1)
relativeOffset = relativeOffset(1) relativeOffset = relativeOffset(1)
nextPage.textLines.forEach { textLine -> nextPage.textLines.forEach { textLine ->
@ -108,6 +109,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
textLine.isReadAloud textLine.isReadAloud
) )
} }
if (!pageFactory.hasNextPlus()) return
relativeOffset = relativeOffset(2) relativeOffset = relativeOffset(2)
if (relativeOffset < ChapterProvider.visibleHeight) { if (relativeOffset < ChapterProvider.visibleHeight) {
relativePage(2).textLines.forEach { textLine -> relativePage(2).textLines.forEach { textLine ->
@ -163,24 +165,20 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} }
pageOffset += offset pageOffset += offset
if (pageOffset > 0) { if (!pageFactory.hasPrev() && pageOffset > 0) {
if (!pageFactory.hasPrev()) { pageOffset = 0f
pageOffset = 0f } else if (!pageFactory.hasNext() && pageOffset < 0) {
} else { pageOffset = 0f
pageFactory.moveToPrev() } else if (pageOffset > 0) {
textPage = pageFactory.currentPage pageFactory.moveToPrev()
pageOffset -= textPage.height textPage = pageFactory.currentPage
upView?.invoke(textPage) pageOffset -= textPage.height
} upView?.invoke(textPage)
} else if (pageOffset < -textPage.height) { } else if (pageOffset < -textPage.height) {
if (!pageFactory.hasNext()) { pageOffset += textPage.height
pageOffset = -textPage.height.toFloat() pageFactory.moveToNext()
} else { textPage = pageFactory.currentPage
pageOffset += textPage.height upView?.invoke(textPage)
pageFactory.moveToNext()
textPage = pageFactory.currentPage
upView?.invoke(textPage)
}
} }
invalidate() invalidate()
} }

@ -22,4 +22,5 @@ abstract class PageFactory<DATA>(protected val dataSource: DataSource) {
abstract fun hasPrev(): Boolean abstract fun hasPrev(): Boolean
abstract fun hasNextPlus(): Boolean
} }

@ -120,7 +120,7 @@ class PageView(context: Context, attrs: AttributeSet) :
if (noAnim) { if (noAnim) {
fillPage(PageDelegate.Direction.PREV) fillPage(PageDelegate.Direction.PREV)
} else { } else {
pageDelegate?.start(PageDelegate.Direction.PREV) pageDelegate?.prevPageByAnim()
} }
} }
@ -128,7 +128,7 @@ class PageView(context: Context, attrs: AttributeSet) :
if (noAnim) { if (noAnim) {
fillPage(PageDelegate.Direction.NEXT) fillPage(PageDelegate.Direction.NEXT)
} else { } else {
pageDelegate?.start(PageDelegate.Direction.NEXT) pageDelegate?.nextPageByAnim()
} }
} }

@ -13,6 +13,10 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
return hasNextChapter() || currentChapter?.isLastIndex(pageIndex) != true return hasNextChapter() || currentChapter?.isLastIndex(pageIndex) != true
} }
override fun hasNextPlus(): Boolean = with(dataSource) {
return hasNextChapter() || pageIndex < (currentChapter?.pageSize() ?: 1) - 2
}
override fun moveToFirst() { override fun moveToFirst() {
ReadBook.setPageIndex(0) ReadBook.setPageIndex(0)
} }
@ -68,6 +72,9 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
?: TextPage(title = it.title).format() ?: TextPage(title = it.title).format()
} }
} }
if (!hasNextChapter()) {
return@with TextPage(text = "")
}
nextChapter?.let { nextChapter?.let {
return@with it.page(0)?.removePageAloudSpan() return@with it.page(0)?.removePageAloudSpan()
?: TextPage(title = it.title).format() ?: TextPage(title = it.title).format()

@ -52,4 +52,19 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
} }
} }
override fun nextPageByAnim() {
if (!hasNext()) return
setDirection(Direction.NEXT)
setBitmap()
setTouchPoint(viewWidth.toFloat(), 0f)
onAnimStart()
}
override fun prevPageByAnim() {
if (!hasPrev()) return
setDirection(Direction.PREV)
setBitmap()
setTouchPoint(0f, 0f)
onAnimStart()
}
} }

@ -12,6 +12,7 @@ import android.widget.Scroller
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.ContentView import io.legado.app.ui.book.read.page.ContentView
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
import io.legado.app.utils.screenshot import io.legado.app.utils.screenshot
@ -157,34 +158,6 @@ abstract class PageDelegate(protected val pageView: PageView) :
} }
} }
fun start(direction: Direction) {
if (isStarted) return
if (direction === Direction.NEXT) {
val x = viewWidth.toFloat()
val y = viewHeight.toFloat()
//初始化动画
setStartPoint(x, y, false)
//设置点击点
setTouchPoint(x, y, false)
//设置方向
if (!hasNext()) {
return
}
} else {
val x = 0.toFloat()
val y = viewHeight.toFloat()
//初始化动画
setStartPoint(x, y, false)
//设置点击点
setTouchPoint(x, y, false)
//设置方向方向
if (!hasPrev()) {
return
}
}
onAnimStart()
}
open fun onAnimStart() {}//scroller start open fun onAnimStart() {}//scroller start
open fun onDraw(canvas: Canvas) {}//绘制 open fun onDraw(canvas: Canvas) {}//绘制
@ -193,6 +166,10 @@ abstract class PageDelegate(protected val pageView: PageView) :
open fun onScroll() {}//移动contentView, slidePage open fun onScroll() {}//移动contentView, slidePage
abstract fun nextPageByAnim()
abstract fun prevPageByAnim()
@CallSuper @CallSuper
open fun setDirection(direction: Direction) { open fun setDirection(direction: Direction) {
mDirection = direction mDirection = direction
@ -260,21 +237,14 @@ abstract class PageDelegate(protected val pageView: PageView) :
if (centerRectF.contains(x, y)) { if (centerRectF.contains(x, y)) {
pageView.callBack.clickCenter() pageView.callBack.clickCenter()
setTouchPoint(x, y) setTouchPoint(x, y)
} else { } else if (ReadBookConfig.clickTurnPage) {
if (x > viewWidth / 2 || if (x > viewWidth / 2 ||
AppConfig.clickAllNext AppConfig.clickAllNext
) { ) {
//设置动画方向 nextPageByAnim()
if (!hasNext()) return true
setDirection(Direction.NEXT)
setBitmap()
} else { } else {
if (!hasPrev()) return true prevPageByAnim()
setDirection(Direction.PREV)
setBitmap()
} }
setTouchPoint(x, y)
onAnimStart()
} }
return true return true
} }

@ -2,6 +2,7 @@ package io.legado.app.ui.book.read.page.delegate
import android.view.MotionEvent import android.view.MotionEvent
import android.view.VelocityTracker import android.view.VelocityTracker
import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
import kotlin.math.abs import kotlin.math.abs
@ -58,4 +59,12 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
super.onDestroy() super.onDestroy()
mVelocity.recycle() mVelocity.recycle()
} }
override fun nextPageByAnim() {
startScroll(0, 0, 0, -ChapterProvider.visibleHeight)
}
override fun prevPageByAnim() {
startScroll(0, 0, 0, ChapterProvider.visibleHeight)
}
} }

@ -83,7 +83,7 @@ class BookSourceDebugActivity :
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_scan -> { R.id.menu_scan -> {
startActivityForResult<QrCodeActivity>(qrRequestCode) startActivityForResult<QrCodeActivity>(qrRequestCode)
} }
} }

@ -24,6 +24,7 @@ import io.legado.app.data.entities.rule.*
import io.legado.app.lib.dialogs.alert 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.ui.book.source.debug.BookSourceDebugActivity import io.legado.app.ui.book.source.debug.BookSourceDebugActivity
import io.legado.app.ui.login.SourceLogin
import io.legado.app.ui.widget.KeyboardToolPop import io.legado.app.ui.widget.KeyboardToolPop
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
@ -67,22 +68,20 @@ class BookSourceEditActivity :
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_save -> { R.id.menu_save -> getSource().let { source ->
val source = getSource()
if (checkSource(source)) { if (checkSource(source)) {
viewModel.save(source) { setResult(Activity.RESULT_OK); finish() } viewModel.save(source) { setResult(Activity.RESULT_OK); finish() }
} }
} }
R.id.menu_debug_source -> { R.id.menu_debug_source -> getSource().let { source ->
val source = getSource()
if (checkSource(source)) { if (checkSource(source)) {
viewModel.save(source) { viewModel.save(source) {
startActivity<BookSourceDebugActivity>(Pair("key", source.bookSourceUrl)) startActivity<BookSourceDebugActivity>(Pair("key", source.bookSourceUrl))
} }
} }
} }
R.id.menu_copy_source -> { R.id.menu_copy_source -> getSource().let { source ->
GSON.toJson(getSource())?.let { sourceStr -> GSON.toJson(source)?.let { sourceStr ->
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
clipboard?.setPrimaryClip(ClipData.newPlainText(null, sourceStr)) clipboard?.setPrimaryClip(ClipData.newPlainText(null, sourceStr))
} }
@ -101,6 +100,18 @@ class BookSourceEditActivity :
toast(R.string.can_not_open) toast(R.string.can_not_open)
} }
} }
R.id.menu_login -> getSource().let {
if (checkSource(it)) {
if (it.loginUrl.isNullOrEmpty()) {
toast(R.string.source_no_login)
} else {
startActivity<SourceLogin>(
Pair("sourceUrl", it.bookSourceUrl),
Pair("loginUrl", it.loginUrl)
)
}
}
}
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.SubMenu import android.view.SubMenu
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -20,14 +21,13 @@ import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.dialogs.* import io.legado.app.lib.dialogs.*
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryTextColor import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.service.help.CheckSource import io.legado.app.service.help.CheckSource
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.qrcode.QrCodeActivity
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
@ -39,7 +39,7 @@ import kotlinx.android.synthetic.main.view_search.*
import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivity
import org.jetbrains.anko.startActivityForResult import org.jetbrains.anko.startActivityForResult
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.FileNotFoundException import java.io.File
class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity_book_source), class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity_book_source),
PopupMenu.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener,
@ -50,7 +50,8 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
get() = getViewModel(BookSourceViewModel::class.java) get() = getViewModel(BookSourceViewModel::class.java)
private val importRecordKey = "bookSourceRecordKey" private val importRecordKey = "bookSourceRecordKey"
private val qrRequestCode = 101 private val qrRequestCode = 101
private val importSource = 132 private val importRequestCode = 132
private val exportRequestCode = 65
private lateinit var adapter: BookSourceAdapter private lateinit var adapter: BookSourceAdapter
private var bookSourceLiveDate: LiveData<List<BookSource>>? = null private var bookSourceLiveDate: LiveData<List<BookSource>>? = null
private var groups = linkedSetOf<String>() private var groups = linkedSetOf<String>()
@ -82,7 +83,8 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode) R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode)
R.id.menu_group_manage -> R.id.menu_group_manage ->
GroupManageDialog().show(supportFragmentManager, "groupManage") GroupManageDialog().show(supportFragmentManager, "groupManage")
R.id.menu_import_source_local -> selectFileSys() R.id.menu_import_source_local -> FilePicker
.selectFile(this, importRequestCode, "text/*", arrayOf("txt", "json"))
R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_onLine -> showImportDialog()
} }
if (item.groupId == R.id.source_group) { if (item.groupId == R.id.source_group) {
@ -187,7 +189,7 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection()) R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection())
R.id.menu_enable_explore -> viewModel.enableSelectExplore(adapter.getSelection()) R.id.menu_enable_explore -> viewModel.enableSelectExplore(adapter.getSelection())
R.id.menu_disable_explore -> viewModel.disableSelectExplore(adapter.getSelection()) R.id.menu_disable_explore -> viewModel.disableSelectExplore(adapter.getSelection())
R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelection()) R.id.menu_export_selection -> FilePicker.selectFolder(this, exportRequestCode)
R.id.menu_check_source -> CheckSource.start(this, adapter.getSelection()) R.id.menu_check_source -> CheckSource.start(this, adapter.getSelection())
} }
return true return true
@ -235,47 +237,10 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
}.show().applyTint() }.show().applyTint()
} }
private fun selectFileSys() {
try {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.type = "text/*"//设置类型
startActivityForResult(intent, importSource)
} catch (e: Exception) {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
selectFile()
}
.request()
}
}
private fun selectFile() {
FileChooserDialog.show(
supportFragmentManager, importSource,
allowExtensions = arrayOf("txt", "json")
)
}
override fun upCountView() { override fun upCountView() {
select_action_bar.upCountView(adapter.getSelection().size, adapter.getActualItemCount()) select_action_bar.upCountView(adapter.getSelection().size, adapter.getActualItemCount())
} }
override fun onFilePicked(requestCode: Int, currentPath: String) {
if (requestCode == importSource) {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSourceFromFilePath(currentPath) { msg ->
title_bar.snackbar(msg)
}
}
}
override fun onQueryTextChange(newText: String?): Boolean { override fun onQueryTextChange(newText: String?): Boolean {
newText?.let { newText?.let {
initLiveDataBookSource(it) initLiveDataBookSource(it)
@ -307,6 +272,21 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
viewModel.topSource(bookSource) viewModel.topSource(bookSource)
} }
override fun onFilePicked(requestCode: Int, currentPath: String) {
when (requestCode) {
exportRequestCode -> viewModel.exportSelection(
adapter.getSelection(),
File(currentPath)
)
importRequestCode -> {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSourceFromFilePath(currentPath) { msg ->
title_bar.snackbar(msg)
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
when (requestCode) { when (requestCode) {
@ -318,7 +298,7 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
} }
} }
} }
importSource -> if (resultCode == Activity.RESULT_OK) { importRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri -> data?.data?.let { uri ->
try { try {
uri.readText(this)?.let { uri.readText(this)?.let {
@ -328,22 +308,24 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
title_bar.snackbar(msg) title_bar.snackbar(msg)
} }
} }
} catch (e: FileNotFoundException) {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
selectFileSys()
}
.request()
} catch (e: Exception) { } catch (e: Exception) {
toast(e.localizedMessage ?: "ERROR") toast(e.localizedMessage ?: "ERROR")
} }
} }
} }
exportRequestCode -> {
data?.data?.let { uri ->
if (uri.toString().isContentPath()) {
DocumentFile.fromTreeUri(this, uri)?.let {
viewModel.exportSelection(adapter.getSelection(), it)
}
} else {
uri.path?.let {
viewModel.exportSelection(adapter.getSelection(), File(it))
}
}
}
}
} }
} }

@ -2,6 +2,7 @@ package io.legado.app.ui.book.source.manage
import android.app.Application import android.app.Application
import android.text.TextUtils import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.JsonPath
import io.legado.app.App import io.legado.app.App
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
@ -89,12 +90,24 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun exportSelection(sources: LinkedHashSet<BookSource>) { fun exportSelection(sources: LinkedHashSet<BookSource>, file: File) {
execute { execute {
val json = GSON.toJson(sources) val json = GSON.toJson(sources)
val file = FileUtils.createFileIfNotExist(file, "exportBookSource.json")
FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportBookSource.json") .writeText(json)
file.writeText(json) }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {
context.toast("导出失败\n${it.localizedMessage}")
}
}
fun exportSelection(sources: LinkedHashSet<BookSource>, doc: DocumentFile) {
execute {
val json = GSON.toJson(sources)
doc.findFile("exportBookSource.json")?.delete()
doc.createFile("", "exportBookSource.json")
?.writeText(context, json)
}.onSuccess { }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}") context.toast("成功导出至\n${Backup.exportPath}")
}.onError { }.onError {

@ -3,7 +3,6 @@ package io.legado.app.ui.config
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import io.legado.app.App import io.legado.app.App
@ -17,13 +16,13 @@ import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.ImportOldData import io.legado.app.help.storage.ImportOldData
import io.legado.app.help.storage.Restore import io.legado.app.help.storage.Restore
import io.legado.app.help.storage.WebDavHelp import io.legado.app.help.storage.WebDavHelp
import io.legado.app.lib.dialogs.alert import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import io.legado.app.utils.isContentPath import io.legado.app.utils.isContentPath
import io.legado.app.utils.toast import io.legado.app.utils.toast
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.File
object BackupRestoreUi { object BackupRestoreUi {
private const val selectFolderRequestCode = 21 private const val selectFolderRequestCode = 21
@ -79,31 +78,7 @@ object BackupRestoreUi {
} }
fun selectBackupFolder(fragment: Fragment, requestCode: Int = selectFolderRequestCode) { fun selectBackupFolder(fragment: Fragment, requestCode: Int = selectFolderRequestCode) {
fragment.alert { FilePicker.selectFolder(fragment, requestCode)
titleResource = R.string.select_folder
items(fragment.resources.getStringArray(R.array.select_folder).toList()) { _, index ->
when (index) {
0 -> backupUsePermission(fragment, requestCode = requestCode)
1 -> {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
fragment.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
fragment.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> {
FileChooserDialog.show(
fragment.childFragmentManager,
requestCode,
mode = FileChooserDialog.DIRECTORY
)
}
}
}
}.show()
} }
fun restore(fragment: Fragment) { fun restore(fragment: Fragment) {
@ -120,13 +95,13 @@ object BackupRestoreUi {
Restore.restore(fragment.requireContext(), backupPath) Restore.restore(fragment.requireContext(), backupPath)
fragment.toast(R.string.restore_success) fragment.toast(R.string.restore_success)
} else { } else {
selectRestoreFolder(fragment) selectBackupFolder(fragment, restoreSelectRequestCode)
} }
} else { } else {
restoreUsePermission(fragment, backupPath) restoreUsePermission(fragment, backupPath)
} }
} else { } else {
selectRestoreFolder(fragment) selectBackupFolder(fragment, restoreSelectRequestCode)
} }
} }
} }
@ -147,79 +122,8 @@ object BackupRestoreUi {
.request() .request()
} }
private fun selectRestoreFolder(fragment: Fragment) {
fragment.alert {
titleResource = R.string.select_folder
items(fragment.resources.getStringArray(R.array.select_folder).toList()) { _, index ->
when (index) {
0 -> restoreUsePermission(fragment)
1 -> {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
fragment.startActivityForResult(intent, restoreSelectRequestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
fragment.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> {
FileChooserDialog.show(
fragment.childFragmentManager,
restoreSelectRequestCode,
mode = FileChooserDialog.DIRECTORY
)
}
}
}
}.show()
}
fun importOldData(fragment: Fragment) { fun importOldData(fragment: Fragment) {
fragment.alert { FilePicker.selectFolder(fragment, oldDataRequestCode)
titleResource = R.string.select_folder
items(fragment.resources.getStringArray(R.array.select_folder).toList()) { _, index ->
when (index) {
0 -> importOldUsePermission(fragment)
1 -> {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
fragment.startActivityForResult(intent, oldDataRequestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
fragment.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> {
PermissionsCompat.Builder(fragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
FileChooserDialog.show(
fragment.childFragmentManager,
oldDataRequestCode,
mode = FileChooserDialog.DIRECTORY
)
}
.request()
}
}
}
}.show()
}
private fun importOldUsePermission(fragment: Fragment) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
fragment.toast(R.string.a10_permission_toast)
}
PermissionsCompat.Builder(fragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
ImportOldData.import(fragment.requireContext())
}
.request()
} }
fun onFilePicked(requestCode: Int, currentPath: String) { fun onFilePicked(requestCode: Int, currentPath: String) {
@ -243,6 +147,9 @@ object BackupRestoreUi {
selectFolderRequestCode -> { selectFolderRequestCode -> {
AppConfig.backupPath = currentPath AppConfig.backupPath = currentPath
} }
oldDataRequestCode -> {
ImportOldData.import(App.INSTANCE, File(currentPath))
}
} }
} }

@ -16,12 +16,10 @@ 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.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
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.receiver.SharedReceiverActivity import io.legado.app.receiver.SharedReceiverActivity
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.ui.widget.number.NumberPickerDialog import io.legado.app.ui.widget.number.NumberPickerDialog
import io.legado.app.utils.* import io.legado.app.utils.*
@ -124,38 +122,9 @@ class OtherConfigFragment : PreferenceFragmentCompat(),
} }
private fun selectDownloadPath() { private fun selectDownloadPath() {
alert { FilePicker.selectFolder(this, requestCodeDownloadPath) {
titleResource = R.string.select_folder removePref(PreferKey.downloadPath)
items(resources.getStringArray(R.array.select_folder).toList()) { _, i -> }
when (i) {
0 -> {
removePref(PreferKey.downloadPath)
}
1 -> {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(intent, requestCodeDownloadPath)
} catch (e: Exception) {
e.printStackTrace()
toast(e.localizedMessage ?: "ERROR")
}
}
2 -> PermissionsCompat.Builder(this@OtherConfigFragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
FileChooserDialog.show(
childFragmentManager,
requestCodeDownloadPath,
mode = FileChooserDialog.DIRECTORY,
initPath = BookHelp.downloadPath
)
}
.request()
}
}
}.show()
} }
override fun onFilePicked(requestCode: Int, currentPath: String) { override fun onFilePicked(requestCode: Int, currentPath: String) {

@ -52,7 +52,10 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
"transparentStatusBar" -> { "transparentStatusBar" -> {
recreateActivities() recreateActivities()
} }
"colorPrimary", "colorAccent", "colorBackground" -> { "colorPrimary",
"colorAccent",
"colorBackground",
"colorBottomBackground" -> {
if (backgroundIsDark(sharedPreferences)) { if (backgroundIsDark(sharedPreferences)) {
alert { alert {
title = "白天背景太暗" title = "白天背景太暗"
@ -73,7 +76,10 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
upTheme(false) upTheme(false)
} }
} }
"colorPrimaryNight", "colorAccentNight", "colorBackgroundNight" -> { "colorPrimaryNight",
"colorAccentNight",
"colorBackgroundNight",
"colorBottomBackgroundNight" -> {
if (backgroundIsLight(sharedPreferences)) { if (backgroundIsLight(sharedPreferences)) {
alert { alert {
title = "夜间背景太亮" title = "夜间背景太亮"

@ -16,17 +16,15 @@ import io.legado.app.constant.EventBus
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.service.help.Download import io.legado.app.service.help.Download
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.activity_download.* import kotlinx.android.synthetic.main.activity_download.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jetbrains.anko.alert
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
@ -126,44 +124,14 @@ class DownloadActivity : VMBaseActivity<DownloadViewModel>(R.layout.activity_dow
override fun export(position: Int) { override fun export(position: Int) {
exportPosition = position exportPosition = position
alert { FilePicker.selectFolder(this, exportRequestCode) {
titleResource = R.string.select_folder val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey)
items(resources.getStringArray(R.array.select_folder).toList()) { _, index -> if (path.isNullOrEmpty()) {
when (index) { toast("没有默认路径")
0 -> { } else {
val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey) startExport(path)
if (path.isNullOrEmpty()) {
toast("没有默认路径")
} else {
startExport(path)
}
}
1 -> {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(intent, exportRequestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
toast(e.localizedMessage ?: "ERROR")
}
}
2 -> {
PermissionsCompat.Builder(this@DownloadActivity)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
FileChooserDialog.show(
supportFragmentManager,
exportRequestCode,
mode = FileChooserDialog.DIRECTORY
)
}
.request()
}
}
} }
}.show() }
} }
private fun startExport(path: String) { private fun startExport(path: String) {

@ -42,7 +42,7 @@ class FileChooserDialog : DialogFragment(),
isShowHomeDir: Boolean = false, isShowHomeDir: Boolean = false,
isShowUpDir: Boolean = true, isShowUpDir: Boolean = true,
isShowHideDir: Boolean = false, isShowHideDir: Boolean = false,
allowExtensions: Array<String?>? = null, allowExtensions: Array<String>? = null,
menus: Array<String>? = null menus: Array<String>? = null
) { ) {
FileChooserDialog().apply { FileChooserDialog().apply {
@ -61,7 +61,7 @@ class FileChooserDialog : DialogFragment(),
} }
} }
override var allowExtensions: Array<String?>? = null override var allowExtensions: Array<String>? = null
override val isOnlyListDir: Boolean override val isOnlyListDir: Boolean
get() = mode == DIRECTORY get() = mode == DIRECTORY
override var isShowHomeDir: Boolean = false override var isShowHomeDir: Boolean = false

@ -0,0 +1,191 @@
package io.legado.app.ui.filechooser
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import io.legado.app.R
import io.legado.app.base.BaseActivity
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.dialogs.alert
import io.legado.app.utils.toast
import org.jetbrains.anko.toast
@Suppress("unused")
object FilePicker {
fun selectFolder(activity: AppCompatActivity, requestCode: Int, default: (() -> Unit)? = null) {
activity.alert(titleResource = R.string.select_folder) {
val selectList =
activity.resources.getStringArray(R.array.select_folder).toMutableList()
default ?: let {
selectList.removeAt(0)
}
items(selectList) { _, index ->
when (if (default == null) index + 1 else index) {
0 -> default?.invoke()
1 -> {
try {
val intent = getSelectDirIntent()
activity.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
activity.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> checkPermissions(activity) {
FileChooserDialog.show(
activity.supportFragmentManager,
requestCode,
mode = FileChooserDialog.DIRECTORY
)
}
}
}
}.show()
}
fun selectFolder(fragment: Fragment, requestCode: Int, default: (() -> Unit)? = null) {
fragment.requireContext()
.alert(titleResource = R.string.select_folder) {
val selectList =
fragment.resources.getStringArray(R.array.select_folder).toMutableList()
default ?: let {
selectList.removeAt(0)
}
items(selectList) { _, index ->
when (if (default == null) index + 1 else index) {
0 -> default?.invoke()
1 -> {
try {
val intent = getSelectDirIntent()
fragment.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
fragment.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> checkPermissions(fragment) {
FileChooserDialog.show(
fragment.childFragmentManager,
requestCode,
mode = FileChooserDialog.DIRECTORY
)
}
}
}
}.show()
}
fun selectFile(
activity: BaseActivity,
requestCode: Int,
type: String,
allowExtensions: Array<String>?,
default: (() -> Unit)? = null
) {
activity.alert(titleResource = R.string.select_file) {
val selectList =
activity.resources.getStringArray(R.array.select_folder).toMutableList()
default ?: let {
selectList.removeAt(0)
}
items(selectList) { _, index ->
when (if (default == null) index + 1 else index) {
0 -> default?.invoke()
1 -> {
try {
val intent = getSelectFileIntent()
intent.type = type//设置类型
activity.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
activity.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> checkPermissions(activity) {
FileChooserDialog.show(
activity.supportFragmentManager,
requestCode,
mode = FileChooserDialog.FILE,
allowExtensions = allowExtensions
)
}
}
}
}.show()
}
fun selectFile(
fragment: Fragment,
requestCode: Int,
type: String,
allowExtensions: Array<String>,
default: (() -> Unit)? = null
) {
fragment.requireContext()
.alert(titleResource = R.string.select_file) {
val selectList =
fragment.resources.getStringArray(R.array.select_folder).toMutableList()
default ?: let {
selectList.removeAt(0)
}
items(selectList) { _, index ->
when (if (default == null) index + 1 else index) {
0 -> default?.invoke()
1 -> {
try {
val intent = getSelectFileIntent()
intent.type = type//设置类型
fragment.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
fragment.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> checkPermissions(fragment) {
FileChooserDialog.show(
fragment.childFragmentManager,
requestCode,
mode = FileChooserDialog.FILE,
allowExtensions = allowExtensions
)
}
}
}
}.show()
}
private fun getSelectFileIntent(): Intent {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
return intent
}
private fun getSelectDirIntent(): Intent {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
return intent
}
private fun checkPermissions(fragment: Fragment, success: (() -> Unit)? = null) {
PermissionsCompat.Builder(fragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
success?.invoke()
}
.request()
}
private fun checkPermissions(activity: AppCompatActivity, success: (() -> Unit)? = null) {
PermissionsCompat.Builder(activity)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
success?.invoke()
}
.request()
}
}

@ -6,9 +6,9 @@ import android.graphics.drawable.Drawable
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.ui.filechooser.FilePickerIcon
import io.legado.app.ui.filechooser.entity.FileItem import io.legado.app.ui.filechooser.entity.FileItem
import io.legado.app.ui.filechooser.utils.ConvertUtils import io.legado.app.ui.filechooser.utils.ConvertUtils
import io.legado.app.ui.filechooser.utils.FilePickerIcon
import io.legado.app.ui.filechooser.utils.FileUtils import io.legado.app.ui.filechooser.utils.FileUtils
import kotlinx.android.synthetic.main.item_path_filepicker.view.* import kotlinx.android.synthetic.main.item_path_filepicker.view.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
@ -122,7 +122,7 @@ class FileAdapter(context: Context, val callBack: CallBack) :
interface CallBack { interface CallBack {
fun onFileClick(position: Int) fun onFileClick(position: Int)
//允许的扩展名 //允许的扩展名
var allowExtensions: Array<String?>? var allowExtensions: Array<String>?
/** /**
* 是否仅仅读取目录 * 是否仅仅读取目录
*/ */

@ -5,8 +5,8 @@ import android.os.Environment
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.ui.filechooser.FilePickerIcon
import io.legado.app.ui.filechooser.utils.ConvertUtils import io.legado.app.ui.filechooser.utils.ConvertUtils
import io.legado.app.ui.filechooser.utils.FilePickerIcon
import kotlinx.android.synthetic.main.item_path_filepicker.view.* import kotlinx.android.synthetic.main.item_path_filepicker.view.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
import java.util.* import java.util.*

@ -1,4 +1,4 @@
package io.legado.app.ui.filechooser; package io.legado.app.ui.filechooser.utils;
/** /**
* Generated by https://github.com/gzu-liyujiang/Image2ByteVar * Generated by https://github.com/gzu-liyujiang/Image2ByteVar

@ -60,7 +60,7 @@ object FileUtils {
@JvmOverloads @JvmOverloads
fun listDirs( fun listDirs(
startDirPath: String, startDirPath: String,
excludeDirs: Array<String?>? = null, @SortType sortType: Int = BY_NAME_ASC excludeDirs: Array<String>? = null, @SortType sortType: Int = BY_NAME_ASC
): Array<File?> { ): Array<File?> {
var excludeDirs1 = excludeDirs var excludeDirs1 = excludeDirs
val dirList = ArrayList<File>() val dirList = ArrayList<File>()
@ -76,7 +76,7 @@ object FileUtils {
}) })
?: return arrayOfNulls(0) ?: return arrayOfNulls(0)
if (excludeDirs1 == null) { if (excludeDirs1 == null) {
excludeDirs1 = arrayOfNulls(0) excludeDirs1 = arrayOf()
} }
for (dir in dirs) { for (dir in dirs) {
val file = dir.absoluteFile val file = dir.absoluteFile
@ -115,7 +115,7 @@ object FileUtils {
@JvmOverloads @JvmOverloads
fun listDirsAndFiles( fun listDirsAndFiles(
startDirPath: String, startDirPath: String,
allowExtensions: Array<String?>? = null allowExtensions: Array<String>? = null
): Array<File?>? { ): Array<File?>? {
val dirs: Array<File?>? val dirs: Array<File?>?
val files: Array<File?>? = if (allowExtensions == null) { val files: Array<File?>? = if (allowExtensions == null) {
@ -189,12 +189,13 @@ object FileUtils {
/** /**
* 列出指定目录下的所有文件 * 列出指定目录下的所有文件
*/ */
fun listFiles(startDirPath: String, allowExtensions: Array<String?>): Array<File?>? { fun listFiles(startDirPath: String, allowExtensions: Array<String>?): Array<File?>? {
val file = File(startDirPath) val file = File(startDirPath)
return file.listFiles { _, name -> return file.listFiles { _, name ->
//返回当前目录所有以某些扩展名结尾的文件 //返回当前目录所有以某些扩展名结尾的文件
val extension = getExtension(name) val extension = getExtension(name)
allowExtensions.contentDeepToString().contains(extension) allowExtensions?.contentDeepToString()?.contains(extension) == true
|| allowExtensions == null
} }
} }
@ -202,7 +203,10 @@ object FileUtils {
* 列出指定目录下的所有文件 * 列出指定目录下的所有文件
*/ */
fun listFiles(startDirPath: String, allowExtension: String?): Array<File?>? { fun listFiles(startDirPath: String, allowExtension: String?): Array<File?>? {
return listFiles(startDirPath, arrayOf(allowExtension)) return if (allowExtension == null)
listFiles(startDirPath, allowExtension = null)
else
listFiles(startDirPath, arrayOf(allowExtension))
} }
/** /**

@ -1,14 +1,79 @@
package io.legado.app.ui.login package io.legado.app.ui.login
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.webkit.CookieManager
import android.webkit.WebView
import android.webkit.WebViewClient
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseActivity import io.legado.app.base.BaseActivity
import io.legado.app.help.http.CookieStore
import io.legado.app.utils.snackbar
import kotlinx.android.synthetic.main.activity_source_login.*
class SourceLogin : BaseActivity(R.layout.activity_source_login) { class SourceLogin : BaseActivity(R.layout.activity_source_login) {
var sourceUrl: String? = null
var loginUrl: String? = null
var checking = false
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
sourceUrl = intent.getStringExtra("sourceUrl")
loginUrl = intent.getStringExtra("loginUrl")
title = getString(R.string.login_source, sourceUrl)
initWebView()
}
@SuppressLint("SetJavaScriptEnabled")
private fun initWebView() {
val settings = web_view.settings
settings.setSupportZoom(true)
settings.builtInZoomControls = true
settings.javaScriptEnabled = true
val cookieManager = CookieManager.getInstance()
web_view.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
val cookie = cookieManager.getCookie(url)
sourceUrl?.let {
CookieStore.setCookie(it, cookie)
}
super.onPageStarted(view, url, favicon)
}
override fun onPageFinished(view: WebView?, url: String?) {
val cookie = cookieManager.getCookie(url)
sourceUrl?.let {
CookieStore.setCookie(it, cookie)
}
if (checking) {
finish()
}
super.onPageFinished(view, url)
}
}
web_view.loadUrl(loginUrl)
}
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.source_login, menu)
return super.onCompatCreateOptionsMenu(menu)
}
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_success -> {
if (!checking) {
checking = true
title_bar.snackbar(R.string.check_host_cookie)
web_view.loadUrl(sourceUrl)
}
}
}
return super.onCompatOptionsItemSelected(item)
} }
} }

@ -7,7 +7,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.github.houbb.opencc4j.util.ZhConverterUtil
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import io.legado.app.App import io.legado.app.App
import io.legado.app.BuildConfig import io.legado.app.BuildConfig
@ -16,7 +15,6 @@ import io.legado.app.base.VMBaseActivity
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.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Backup
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
@ -53,21 +51,12 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
override fun onPostCreate(savedInstanceState: Bundle?) { override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState) super.onPostCreate(savedInstanceState)
upVersion() upVersion()
//初始化简繁转换引擎
when (AppConfig.chineseConverterType) {
1 -> Coroutine.async { ZhConverterUtil.toSimple("初始化") }
2 -> Coroutine.async { ZhConverterUtil.toTraditional("初始化") }
}
//自动更新书籍 //自动更新书籍
if (AppConfig.autoRefreshBook) { if (AppConfig.autoRefreshBook) {
view_pager_main.postDelayed({ view_pager_main.postDelayed({
viewModel.upChapterList() viewModel.upChapterList()
}, 1000) }, 1000)
} }
//清楚过期数据
view_pager_main.postDelayed({
viewModel.clearExpiredData()
}, 3000)
} }
override fun onNavigationItemSelected(item: MenuItem): Boolean { override fun onNavigationItemSelected(item: MenuItem): Boolean {

@ -13,7 +13,6 @@ import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import java.util.concurrent.TimeUnit
class MainViewModel(application: Application) : BaseViewModel(application) { class MainViewModel(application: Application) : BaseViewModel(application) {
val updateList = hashSetOf<String>() val updateList = hashSetOf<String>()
@ -54,13 +53,6 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
} }
} }
fun clearExpiredData() {
execute {
App.db.searchBookDao()
.clearExpired(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1))
}
}
fun initRss() { fun initRss() {
execute { execute {
val url = "https://gitee.com/alanskycn/yuedu/raw/master/JS/RSS/rssSource" val url = "https://gitee.com/alanskycn/yuedu/raw/master/JS/RSS/rssSource"

@ -2,6 +2,7 @@ package io.legado.app.ui.main.bookshelf
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
@ -17,7 +18,10 @@ import io.legado.app.constant.AppConst
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.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.lib.dialogs.* 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.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.book.arrange.ArrangeBookActivity import io.legado.app.ui.book.arrange.ArrangeBookActivity
@ -27,6 +31,7 @@ import io.legado.app.ui.download.DownloadActivity
import io.legado.app.ui.importbook.ImportBookActivity import io.legado.app.ui.importbook.ImportBookActivity
import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.ui.widget.text.AutoCompleteTextView
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.dialog_bookshelf_config.view.*
import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_edit_text.view.*
import kotlinx.android.synthetic.main.fragment_bookshelf.* import kotlinx.android.synthetic.main.fragment_bookshelf.*
import kotlinx.android.synthetic.main.view_tab_layout.* import kotlinx.android.synthetic.main.view_tab_layout.*
@ -61,7 +66,7 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
super.onCompatOptionsItemSelected(item) super.onCompatOptionsItemSelected(item)
when (item.itemId) { when (item.itemId) {
R.id.menu_search -> startActivity<SearchActivity>() R.id.menu_search -> startActivity<SearchActivity>()
R.id.menu_bookshelf_layout -> selectBookshelfLayout() R.id.menu_bookshelf_layout -> configBookshelf()
R.id.menu_group_manage -> GroupManageDialog() R.id.menu_group_manage -> GroupManageDialog()
.show(childFragmentManager, "groupManageDialog") .show(childFragmentManager, "groupManageDialog")
R.id.menu_add_local -> startActivity<ImportBookActivity>() R.id.menu_add_local -> startActivity<ImportBookActivity>()
@ -145,14 +150,35 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
} }
} }
private fun selectBookshelfLayout() { @SuppressLint("InflateParams")
selector( private fun configBookshelf() {
title = "选择书架布局", requireContext().alert(titleResource = R.string.bookshelf_layout) {
items = resources.getStringArray(R.array.bookshelf_layout).toList() val bookshelfLayout = getPrefInt(PreferKey.bookshelfLayout)
) { _, index -> val bookshelfSort = getPrefInt(PreferKey.bookshelfSort)
putPrefInt(PreferKey.bookshelfLayout, index) val root = LayoutInflater.from(requireContext())
activity?.recreate() .inflate(R.layout.dialog_bookshelf_config, null).apply {
} rg_layout.checkByIndex(bookshelfLayout)
rg_sort.checkByIndex(bookshelfSort)
}
customView = root
okButton {
root.apply {
var changed = false
if (bookshelfLayout != rg_layout.getCheckedIndex()) {
putPrefInt(PreferKey.bookshelfLayout, rg_layout.getCheckedIndex())
changed = true
}
if (bookshelfSort != rg_sort.getCheckedIndex()) {
putPrefInt(PreferKey.bookshelfSort, rg_sort.getCheckedIndex())
changed = true
}
if (changed) {
activity?.recreate()
}
}
}
noButton()
}.show().applyTint()
} }
@SuppressLint("InflateParams") @SuppressLint("InflateParams")

@ -103,10 +103,16 @@ class BooksFragment : BaseFragment(R.layout.fragment_books),
-3 -> App.db.bookDao().observeAudio() -3 -> App.db.bookDao().observeAudio()
else -> App.db.bookDao().observeByGroup(groupId) else -> App.db.bookDao().observeByGroup(groupId)
} }
bookshelfLiveData?.observe(this, Observer { bookshelfLiveData?.observe(this, Observer { list ->
val books = when (getPrefInt(PreferKey.bookshelfSort)) {
1 -> list.sortedByDescending { it.latestChapterTime }
2 -> list.sortedBy { it.name }
3 -> list.sortedBy { it.order }
else -> list.sortedByDescending { it.durChapterTime }
}
val diffResult = DiffUtil val diffResult = DiffUtil
.calculateDiff(BooksDiffCallBack(ArrayList(booksAdapter.getItems()), it)) .calculateDiff(BooksDiffCallBack(ArrayList(booksAdapter.getItems()), books))
booksAdapter.setItems(it, diffResult) booksAdapter.setItems(books, diffResult)
}) })
} }

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.SubMenu import android.view.SubMenu
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -19,13 +20,14 @@ import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.BookHelp
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
import io.legado.app.help.permission.Permissions import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.dialogs.* import io.legado.app.lib.dialogs.*
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryTextColor import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.ui.replacerule.edit.ReplaceEditDialog import io.legado.app.ui.replacerule.edit.ReplaceEditDialog
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
@ -36,7 +38,6 @@ import kotlinx.android.synthetic.main.dialog_edit_text.view.*
import kotlinx.android.synthetic.main.view_search.* import kotlinx.android.synthetic.main.view_search.*
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.File import java.io.File
import java.io.FileNotFoundException
class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activity_replace_rule), class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activity_replace_rule),
@ -47,7 +48,8 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
override val viewModel: ReplaceRuleViewModel override val viewModel: ReplaceRuleViewModel
get() = getViewModel(ReplaceRuleViewModel::class.java) get() = getViewModel(ReplaceRuleViewModel::class.java)
private val importRecordKey = "replaceRuleRecordKey" private val importRecordKey = "replaceRuleRecordKey"
private val importSource = 132 private val importRequestCode = 132
private val exportRequestCode = 65
private lateinit var adapter: ReplaceRuleAdapter private lateinit var adapter: ReplaceRuleAdapter
private var groups = hashSetOf<String>() private var groups = hashSetOf<String>()
private var groupMenu: SubMenu? = null private var groupMenu: SubMenu? = null
@ -177,7 +179,8 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection()) R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection())
R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_onLine -> showImportDialog()
R.id.menu_import_source_local -> selectFileSys() R.id.menu_import_source_local -> FilePicker
.selectFile(this, importRequestCode, "text/*", arrayOf("txt", "json"))
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }
@ -186,7 +189,7 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
when (item?.itemId) { when (item?.itemId) {
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection()) R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection())
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection()) R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection())
R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelection()) R.id.menu_export_selection -> FilePicker.selectFolder(this, exportRequestCode)
} }
return false return false
} }
@ -233,43 +236,6 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
}.show().applyTint() }.show().applyTint()
} }
private fun selectFileSys() {
try {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.type = "text/*"//设置类型
startActivityForResult(intent, importSource)
} catch (e: Exception) {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
selectFile()
}
.request()
}
}
private fun selectFile() {
FileChooserDialog.show(
supportFragmentManager, importSource,
allowExtensions = arrayOf("txt", "json")
)
}
override fun onFilePicked(requestCode: Int, currentPath: String) {
if (requestCode == importSource) {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSource(File(currentPath).readText()) { msg ->
title_bar.snackbar(msg)
}
}
}
override fun onQueryTextChange(newText: String?): Boolean { override fun onQueryTextChange(newText: String?): Boolean {
observeReplaceRuleData("%$newText%") observeReplaceRuleData("%$newText%")
return false return false
@ -279,10 +245,25 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
return false return false
} }
override fun onFilePicked(requestCode: Int, currentPath: String) {
when (requestCode) {
importRequestCode -> {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSource(File(currentPath).readText()) { msg ->
title_bar.snackbar(msg)
}
}
exportRequestCode -> viewModel.exportSelection(
adapter.getSelection(),
File(currentPath)
)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
when (requestCode) { when (requestCode) {
importSource -> if (resultCode == Activity.RESULT_OK) { importRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri -> data?.data?.let { uri ->
try { try {
uri.readText(this)?.let { uri.readText(this)?.let {
@ -292,25 +273,32 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
title_bar.snackbar(msg) title_bar.snackbar(msg)
} }
} }
} catch (e: FileNotFoundException) {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
selectFileSys()
}
.request()
} catch (e: Exception) { } catch (e: Exception) {
toast(e.localizedMessage ?: "ERROR") toast(e.localizedMessage ?: "ERROR")
} }
} }
} }
exportRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
if (uri.toString().isContentPath()) {
DocumentFile.fromTreeUri(this, uri)?.let {
viewModel.exportSelection(adapter.getSelection(), it)
}
} else {
uri.path?.let {
viewModel.exportSelection(adapter.getSelection(), File(it))
}
}
}
}
} }
} }
override fun onDestroy() {
super.onDestroy()
Coroutine.async { BookHelp.upReplaceRules() }
}
override fun upCountView() { override fun upCountView() {
select_action_bar.upCountView(adapter.getSelection().size, adapter.getActualItemCount()) select_action_bar.upCountView(adapter.getSelection().size, adapter.getActualItemCount())
} }

@ -2,6 +2,7 @@ package io.legado.app.ui.replacerule
import android.app.Application import android.app.Application
import android.text.TextUtils import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
@ -9,10 +10,7 @@ import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.HttpHelper
import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.ImportOldData import io.legado.app.help.storage.ImportOldData
import io.legado.app.utils.FileUtils import io.legado.app.utils.*
import io.legado.app.utils.GSON
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.splitNotBlank
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.File import java.io.File
@ -89,12 +87,24 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
} }
} }
fun exportSelection(rules: LinkedHashSet<ReplaceRule>) { fun exportSelection(sources: LinkedHashSet<ReplaceRule>, file: File) {
execute { execute {
val json = GSON.toJson(rules) val json = GSON.toJson(sources)
val file = FileUtils.createFileIfNotExist(file, "exportReplaceRule.json")
FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportReplaceRule.json") .writeText(json)
file.writeText(json) }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {
context.toast("导出失败\n${it.localizedMessage}")
}
}
fun exportSelection(sources: LinkedHashSet<ReplaceRule>, doc: DocumentFile) {
execute {
val json = GSON.toJson(sources)
doc.findFile("exportReplaceRule.json")?.delete()
doc.createFile("", "exportReplaceRule.json")
?.writeText(context, json)
}.onSuccess { }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}") context.toast("成功导出至\n${Backup.exportPath}")
}.onError { }.onError {

@ -23,11 +23,17 @@ class ReplaceEditDialog : DialogFragment(),
companion object { companion object {
fun show(fragmentManager: FragmentManager, id: Long = -1, pattern: String? = null) { fun show(
fragmentManager: FragmentManager,
id: Long = -1,
pattern: String? = null,
isRegex: Boolean = false
) {
val dialog = ReplaceEditDialog() val dialog = ReplaceEditDialog()
val bundle = Bundle() val bundle = Bundle()
bundle.putLong("id", id) bundle.putLong("id", id)
bundle.putString("pattern", pattern) bundle.putString("pattern", pattern)
bundle.putBoolean("isRegex", isRegex)
dialog.arguments = bundle dialog.arguments = bundle
dialog.show(fragmentManager, "editReplace") dialog.show(fragmentManager, "editReplace")
} }
@ -68,6 +74,7 @@ class ReplaceEditDialog : DialogFragment(),
when (item?.itemId) { when (item?.itemId) {
R.id.menu_save -> { R.id.menu_save -> {
viewModel.save(getReplaceRule()) { viewModel.save(getReplaceRule()) {
callBack?.onReplaceRuleSave()
dismiss() dismiss()
} }
} }
@ -94,4 +101,10 @@ class ReplaceEditDialog : DialogFragment(),
replaceRule.scope = et_scope.text.toString() replaceRule.scope = et_scope.text.toString()
return replaceRule return replaceRule
} }
val callBack get() = activity as? CallBack
interface CallBack {
fun onReplaceRuleSave()
}
} }

@ -21,8 +21,9 @@ class ReplaceEditViewModel(application: Application) : BaseViewModel(application
} }
} else { } else {
bundle.getString("pattern")?.let { pattern -> bundle.getString("pattern")?.let { pattern ->
val isRegex = bundle.getBoolean("isRegex")
replaceRuleData.postValue( replaceRuleData.postValue(
ReplaceRule(pattern = pattern) ReplaceRule(pattern = pattern, isRegex = isRegex)
) )
} }
} }

@ -65,7 +65,7 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
} }
private fun initWebView() { private fun initWebView() {
webView.webViewClient = object : WebViewClient() { web_view.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading( override fun shouldOverrideUrlLoading(
view: WebView?, view: WebView?,
request: WebResourceRequest? request: WebResourceRequest?
@ -78,8 +78,19 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
} }
return true return true
} }
@Suppress("DEPRECATION")
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
if (url?.startsWith("http", true) == true) {
return false
}
url?.let {
openUrl(it)
}
return true
}
} }
webView.settings.apply { web_view.settings.apply {
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
domStorageEnabled = true domStorageEnabled = true
allowContentAccess = true allowContentAccess = true
@ -94,23 +105,35 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
val url = NetworkUtils.getAbsoluteURL(it.origin, it.link) val url = NetworkUtils.getAbsoluteURL(it.origin, it.link)
val html = viewModel.clHtml(content) val html = viewModel.clHtml(content)
if (viewModel.rssSource?.loadWithBaseUrl == true) { if (viewModel.rssSource?.loadWithBaseUrl == true) {
webView.loadDataWithBaseURL(url, html, "text/html", "utf-8", url)//不想用baseUrl进else web_view.loadDataWithBaseURL(
url,
html,
"text/html",
"utf-8",
url
)//不想用baseUrl进else
} else { } else {
//webView.loadData(html, "text/html;charset=utf-8", "utf-8")//经测试可以解决中文乱码 //webView.loadData(html, "text/html;charset=utf-8", "utf-8")//经测试可以解决中文乱码
webView.loadDataWithBaseURL(null, html, "text/html;charset=utf-8", "utf-8", url) web_view.loadDataWithBaseURL(
null,
html,
"text/html;charset=utf-8",
"utf-8",
url
)
} }
} }
}) })
viewModel.urlLiveData.observe(this, Observer { viewModel.urlLiveData.observe(this, Observer {
upJavaScriptEnable() upJavaScriptEnable()
webView.loadUrl(it.url, it.headerMap) web_view.loadUrl(it.url, it.headerMap)
}) })
} }
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
private fun upJavaScriptEnable() { private fun upJavaScriptEnable() {
if (viewModel.rssSource?.enableJs == true) { if (viewModel.rssSource?.enableJs == true) {
webView.settings.javaScriptEnabled = true web_view.settings.javaScriptEnabled = true
} }
} }
@ -151,9 +174,9 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
event?.let { event?.let {
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_BACK -> if (event.isTracking && !event.isCanceled && webView.canGoBack()) { KeyEvent.KEYCODE_BACK -> if (event.isTracking && !event.isCanceled && web_view.canGoBack()) {
if (webView.copyBackForwardList().size > 1) { if (web_view.copyBackForwardList().size > 1) {
webView.goBack() web_view.goBack()
return true return true
} }
} }
@ -168,8 +191,8 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
viewModel.textToSpeech.stop() viewModel.textToSpeech.stop()
upTtsMenu(false) upTtsMenu(false)
} else { } else {
webView.settings.javaScriptEnabled = true web_view.settings.javaScriptEnabled = true
webView.evaluateJavascript("document.documentElement.outerHTML") { web_view.evaluateJavascript("document.documentElement.outerHTML") {
val html = StringEscapeUtils.unescapeJson(it) val html = StringEscapeUtils.unescapeJson(it)
val text = Jsoup.clean(html, Whitelist.none()) val text = Jsoup.clean(html, Whitelist.none())
.replace(Regex("""&\w+;"""), "") .replace(Regex("""&\w+;"""), "")

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.SubMenu import android.view.SubMenu
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -20,12 +21,11 @@ import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssSource
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.dialogs.* import io.legado.app.lib.dialogs.*
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryTextColor import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.qrcode.QrCodeActivity
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
@ -38,7 +38,7 @@ import kotlinx.android.synthetic.main.view_search.*
import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivity
import org.jetbrains.anko.startActivityForResult import org.jetbrains.anko.startActivityForResult
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.FileNotFoundException import java.io.File
class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_rss_source), class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_rss_source),
@ -50,7 +50,8 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
get() = getViewModel(RssSourceViewModel::class.java) get() = getViewModel(RssSourceViewModel::class.java)
private val importRecordKey = "rssSourceRecordKey" private val importRecordKey = "rssSourceRecordKey"
private val qrRequestCode = 101 private val qrRequestCode = 101
private val importSource = 124 private val importRequestCode = 124
private val exportRequestCode = 65
private lateinit var adapter: RssSourceAdapter private lateinit var adapter: RssSourceAdapter
private var sourceLiveData: LiveData<List<RssSource>>? = null private var sourceLiveData: LiveData<List<RssSource>>? = null
private var groups = hashSetOf<String>() private var groups = hashSetOf<String>()
@ -79,7 +80,8 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_add -> startActivity<RssSourceEditActivity>() R.id.menu_add -> startActivity<RssSourceEditActivity>()
R.id.menu_import_source_local -> selectFileSys() R.id.menu_import_source_local -> FilePicker
.selectFile(this, importRequestCode, "text/*", arrayOf("txt", "json"))
R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_onLine -> showImportDialog()
R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode) R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode)
R.id.menu_group_manage -> GroupManageDialog() R.id.menu_group_manage -> GroupManageDialog()
@ -96,7 +98,7 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection()) R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection())
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection()) R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection())
R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection()) R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection())
R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelection()) R.id.menu_export_selection -> FilePicker.selectFolder(this, exportRequestCode)
R.id.menu_check_source -> { R.id.menu_check_source -> {
} }
} }
@ -248,46 +250,25 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
}.show().applyTint() }.show().applyTint()
} }
private fun selectFileSys() {
try {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "text/*"//设置类型
startActivityForResult(intent, importSource)
} catch (e: Exception) {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
selectFile()
}
.request()
}
}
private fun selectFile() {
FileChooserDialog.show(
supportFragmentManager, importSource,
allowExtensions = arrayOf("txt", "json")
)
}
override fun onFilePicked(requestCode: Int, currentPath: String) { override fun onFilePicked(requestCode: Int, currentPath: String) {
if (requestCode == importSource) { when (requestCode) {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() importRequestCode -> {
viewModel.importSourceFromFilePath(currentPath) { msg -> Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
title_bar.snackbar(msg) viewModel.importSourceFromFilePath(currentPath) { msg ->
title_bar.snackbar(msg)
}
} }
exportRequestCode -> viewModel.exportSelection(
adapter.getSelection(),
File(currentPath)
)
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
when (requestCode) { when (requestCode) {
importSource -> if (resultCode == Activity.RESULT_OK) { importRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri -> data?.data?.let { uri ->
try { try {
uri.readText(this)?.let { uri.readText(this)?.let {
@ -297,17 +278,6 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
title_bar.snackbar(msg) title_bar.snackbar(msg)
} }
} }
} catch (e: FileNotFoundException) {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
selectFileSys()
}
.request()
} catch (e: Exception) { } catch (e: Exception) {
toast(e.localizedMessage ?: "ERROR") toast(e.localizedMessage ?: "ERROR")
} }
@ -321,6 +291,19 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
} }
} }
} }
exportRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
if (uri.toString().isContentPath()) {
DocumentFile.fromTreeUri(this, uri)?.let {
viewModel.exportSelection(adapter.getSelection(), it)
}
} else {
uri.path?.let {
viewModel.exportSelection(adapter.getSelection(), File(it))
}
}
}
}
} }
} }

@ -2,6 +2,7 @@ package io.legado.app.ui.rss.source.manage
import android.app.Application import android.app.Application
import android.text.TextUtils import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.JsonPath
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
@ -67,12 +68,24 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun exportSelection(sources: LinkedHashSet<RssSource>) { fun exportSelection(sources: LinkedHashSet<RssSource>, file: File) {
execute { execute {
val json = GSON.toJson(sources) val json = GSON.toJson(sources)
val file = FileUtils.createFileIfNotExist(file, "exportRssSource.json")
FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportRssSource.json") .writeText(json)
file.writeText(json) }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {
context.toast("导出失败\n${it.localizedMessage}")
}
}
fun exportSelection(sources: LinkedHashSet<RssSource>, doc: DocumentFile) {
execute {
val json = GSON.toJson(sources)
doc.findFile("exportRssSource.json")?.delete()
doc.createFile("", "exportRssSource.json")
?.writeText(context, json)
}.onSuccess { }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}") context.toast("成功导出至\n${Backup.exportPath}")
}.onError { }.onError {

@ -1,49 +1,55 @@
package io.legado.app.ui.welcome package io.legado.app.ui.welcome
import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import com.github.houbb.opencc4j.util.ZhConverterUtil
import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseActivity import io.legado.app.base.BaseActivity
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainActivity import io.legado.app.ui.main.MainActivity
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import kotlinx.android.synthetic.main.activity_welcome.* import kotlinx.android.synthetic.main.activity_welcome.*
import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivity
import java.util.concurrent.TimeUnit
open class WelcomeActivity : BaseActivity(R.layout.activity_welcome) { open class WelcomeActivity : BaseActivity(R.layout.activity_welcome) {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
iv_bg.setColorFilter(accentColor) iv_book.setColorFilter(accentColor)
vw_title_line.setBackgroundColor(accentColor)
// 避免从桌面启动程序后,会重新实例化入口类的activity // 避免从桌面启动程序后,会重新实例化入口类的activity
if (intent.flags and Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT != 0) { if (intent.flags and Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT != 0) {
finish() finish()
return } else {
init()
} }
val welAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(800) }
welAnimator.startDelay = 100
welAnimator.addUpdateListener { animation ->
val alpha = animation.animatedValue as Float
iv_bg.alpha = alpha
}
welAnimator.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {
startActivity<MainActivity>()
if (getPrefBoolean(getString(R.string.pk_default_read))) {
startActivity<ReadBookActivity>()
}
finish()
}
override fun onAnimationEnd(animation: Animator) = Unit
override fun onAnimationCancel(animation: Animator) = Unit private fun init() {
Coroutine.async {
//清楚过期数据
App.db.searchBookDao()
.clearExpired(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1))
//初始化简繁转换引擎
when (AppConfig.chineseConverterType) {
1 -> ZhConverterUtil.toSimple("初始化")
2 -> ZhConverterUtil.toTraditional("初始化")
else -> null
}
}
root_view.postDelayed({ startMainActivity() }, 300)
}
override fun onAnimationRepeat(animation: Animator) = Unit private fun startMainActivity() {
}) startActivity<MainActivity>()
welAnimator.start() if (getPrefBoolean(getString(R.string.pk_default_read))) {
startActivity<ReadBookActivity>()
}
finish()
} }
} }

@ -9,6 +9,8 @@ import androidx.annotation.MenuRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import io.legado.app.R import io.legado.app.R
import io.legado.app.lib.theme.bottomBackground
import io.legado.app.utils.dp
import io.legado.app.utils.visible import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.view_select_action_bar.view.* import kotlinx.android.synthetic.main.view_select_action_bar.view.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
@ -18,7 +20,8 @@ class SelectActionBar(context: Context, attrs: AttributeSet?) : FrameLayout(cont
private var selMenu: PopupMenu? = null private var selMenu: PopupMenu? = null
init { init {
setBackgroundResource(R.color.background_menu) setBackgroundColor(context.bottomBackground)
elevation = 10.dp.toFloat()
View.inflate(context, R.layout.view_select_action_bar, this) View.inflate(context, R.layout.view_select_action_bar, this)
cb_selected_all.setOnCheckedChangeListener { buttonView, isChecked -> cb_selected_all.setOnCheckedChangeListener { buttonView, isChecked ->
if (buttonView.isPressed) { if (buttonView.isPressed) {

@ -23,13 +23,13 @@ import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
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.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 org.jetbrains.anko.toast
import java.io.File import java.io.File
class FontSelectDialog : BaseDialogFragment(), class FontSelectDialog : BaseDialogFragment(),
@ -107,41 +107,11 @@ class FontSelectDialog : BaseDialogFragment(),
private fun openFolder() { private fun openFolder() {
launch(Main) { launch(Main) {
alert { FilePicker.selectFolder(this@FontSelectDialog, fontFolderRequestCode) {
titleResource = R.string.select_folder val path = "${FileUtils.getSdCardPath()}${File.separator}Fonts"
items(resources.getStringArray(R.array.select_folder).toList()) { _, index -> putPrefString(PreferKey.fontFolder, path)
when (index) { getFontFilesByPermission(path)
0 -> { }
val path = "${FileUtils.getSdCardPath()}${File.separator}Fonts"
putPrefString(PreferKey.fontFolder, path)
getFontFilesByPermission(path)
}
1 -> {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(intent, fontFolderRequestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
requireContext().toast(e.localizedMessage ?: "ERROR")
}
}
2 -> {
PermissionsCompat.Builder(this@FontSelectDialog)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
FileChooserDialog.show(
childFragmentManager,
fontFolderRequestCode,
mode = FileChooserDialog.DIRECTORY
)
}
.request()
}
}
}
}.show()
} }
} }

@ -19,19 +19,17 @@ fun Uri.readBytes(context: Context): ByteArray? {
@Throws(Exception::class) @Throws(Exception::class)
fun Uri.readText(context: Context): String? { fun Uri.readText(context: Context): String? {
if (this.toString().isContentPath()) { readBytes(context)?.let {
return DocumentUtils.readText(context, this) return String(it)
} else {
val path = RealPathUtil.getPath(context, this)
if (path?.isNotEmpty() == true) {
return File(path).readText()
}
} }
return null return null
} }
@Throws(Exception::class) @Throws(Exception::class)
fun Uri.writeBytes(context: Context, byteArray: ByteArray): Boolean { fun Uri.writeBytes(
context: Context,
byteArray: ByteArray
): Boolean {
if (this.toString().isContentPath()) { if (this.toString().isContentPath()) {
return DocumentUtils.writeBytes(context, byteArray, this) return DocumentUtils.writeBytes(context, byteArray, this)
} else { } else {
@ -46,14 +44,5 @@ fun Uri.writeBytes(context: Context, byteArray: ByteArray): Boolean {
@Throws(Exception::class) @Throws(Exception::class)
fun Uri.writeText(context: Context, text: String): Boolean { fun Uri.writeText(context: Context, text: String): Boolean {
if (this.toString().isContentPath()) { return writeBytes(context, text.toByteArray())
return DocumentUtils.writeText(context, text, this) }
} else {
val path = RealPathUtil.getPath(context, this)
if (path?.isNotEmpty() == true) {
File(path).writeText(text)
return true
}
}
return false
}

@ -7,8 +7,10 @@ import android.os.Build
import android.view.View import android.view.View
import android.view.View.* import android.view.View.*
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.RadioGroup
import android.widget.SeekBar import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.get
import io.legado.app.App import io.legado.app.App
@ -69,4 +71,26 @@ fun View.screenshot(): Bitmap? {
fun SeekBar.progressAdd(int: Int) { fun SeekBar.progressAdd(int: Int) {
progress += int progress += int
}
fun RadioGroup.getIndexById(id: Int): Int {
for (i in 0 until this.childCount) {
if (id == get(i).id) {
return i
}
}
return 0
}
fun RadioGroup.getCheckedIndex(): Int {
for (i in 0 until this.childCount) {
if (checkedRadioButtonId == get(i).id) {
return i
}
}
return 0
}
fun RadioGroup.checkByIndex(index: Int) {
check(get(index).id)
} }

@ -1,75 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="680.311dp"
android:height="1360.631dp"
android:viewportWidth="680.311"
android:viewportHeight="1360.631">
<path
android:strokeColor="#82A6F5"
android:strokeWidth="10"
android:strokeMiterLimit="10"
android:pathData="M 225.539 182.693 L 225.539 407.308" />
<path
android:fillColor="#82A6F5"
android:pathData="M282,212.126c0.481,0.289,0.674,0.578,0.674,0.771c0,0.191-0.191,0.48-0.576,0.674l-2.407,1.444v45.431 c0,7.315,0.192,15.497,0.385,18.865v0.674c0,1.348-0.385,1.731-2.406,2.599c-0.865,0.386-1.348,0.578-1.829,0.578 c-0.674,0-0.77-0.578-0.77-2.021v-1.06c0.193-5.679,0.289-14.341,0.289-19.828v-38.501c0-3.945-0.193-10.49-0.385-14.053 L282,212.126z M281.229,194.32c9.433,4.235,12.513,7.315,13.09,9.433c0.097,0.289,0.097,0.675,0.097,1.06 c0,2.214-1.443,5.005-3.369,5.005c-1.251,0-1.731-0.771-2.214-3.369c-0.385-2.021-2.502-5.583-8.181-11.261L281.229,194.32z M336.672,251.302c0,8.759,0.48,10.684,2.406,11.646c0.481,0.289,0.674,0.481,0.674,0.962c0,0.192,0,0.385-0.097,0.674 c-1.54,4.429-3.369,5.294-10.105,5.294c-12.031,0-14.149-0.865-14.149-6.256v-19.924h-7.219 c0.097,18.577-8.855,26.277-24.642,31.956l-0.385-1.347c15.785-8.566,20.117-15.979,20.694-30.608h-6.545v3.272 c0,0.962-0.192,1.348-1.637,2.021c-1.059,0.481-1.829,0.771-2.214,0.771c-0.577,0-0.77-0.481-0.77-1.251v-0.866 c0.192-2.6,0.289-4.62,0.289-9.048v-8.663c0-1.829-0.097-5.486-0.289-7.894l5.102,3.946h16.748 c3.272-5.968,5.679-10.78,8.181-17.614l5.68,2.6c0.385,0.192,0.577,0.385,0.577,0.577c0,0.289-0.192,0.481-0.673,0.675 c-2.118,0.962-2.407,1.154-11.743,13.764h9.048l2.502-3.465l5.39,3.85c0.386,0.289,0.578,0.481,0.578,0.771 s-2.214,1.732-2.694,2.021v7.412c0,3.08,0,5.102,0.289,7.796v0.289c0,1.349-3.851,2.695-4.14,2.695 c-0.577,0-0.577-0.385-0.577-3.658h-7.123v20.117c0,1.731,0.771,2.406,8.278,2.406c5.679,0,6.545-0.866,7.123-14.919H336.672z M297.593,209.335c8.566,4.717,10.78,6.738,10.78,9.723c0,2.405-2.021,4.042-3.08,4.042c-1.251,0-1.829-1.06-2.214-4.14 c-0.289-2.118-1.636-4.332-6.16-8.663L297.593,209.335z M326.95,241.58v-13.475h-29.646v13.475H326.95z M352.072,201.731 c0.385,0.385,0.674,0.674,0.674,0.867c0,0.289-0.289,0.48-0.771,0.77l-2.695,1.637v69.977c0,5.583-0.577,7.123-6.545,9.336 c-0.289,0.096-0.48,0.192-0.674,0.192c-0.385,0-0.674-0.289-0.771-0.866c-0.48-2.118-2.021-3.946-11.068-6.545l0.096-1.251 c5.391,1.059,8.855,1.54,10.973,1.54c3.081,0,3.563-0.964,3.563-2.792v-70.938h-35.421c-2.021,0-4.813,0.385-6.449,1.059 l-2.695-3.561c2.406,0.192,4.813,0.385,7.026,0.385h35.517l3.851-4.428L352.072,201.731z" />
<path
android:fillColor="#82A6F5"
android:pathData="M298.748,378.356c-8.47,7.219-13.283,11.743-16.555,15.688c-0.578,0.675-0.867,0.962-1.251,0.962 c-0.289,0-0.481-0.191-0.771-0.576l-3.85-4.813c2.695-1.155,4.716-3.85,4.716-6.063v-37.25l-6.834,0.385 c-2.599,0.191-3.369,0.385-4.813,0.77l-2.889-2.983c2.889,0.096,4.429,0.096,6.643,0l7.7-0.386l2.888-3.85l5.967,4.813 c0.481,0.385,0.674,0.673,0.674,0.865c0,0.289-0.289,0.481-0.77,0.771l-4.14,2.213v35.902l12.417-7.7L298.748,378.356z M276.417,313.289c5.391,2.984,14.63,9.625,14.63,13.283c0,2.117-1.443,4.62-3.08,4.62c-1.059,0-1.539-0.578-2.31-2.984 c-0.866-2.694-4.235-8.952-9.914-13.957L276.417,313.289z M354.286,372.196c0.48,0.481,0.675,0.77,0.675,1.059 s-0.289,0.386-0.964,0.386h-25.796c-0.674,1.731-1.442,3.465-2.502,5.101c9.818,3.658,18.866,9.145,21.753,12.224 c1.349,1.444,1.828,3.08,1.828,4.524c0,1.925-0.866,3.465-2.021,3.465c-0.867,0-1.638-0.48-4.235-4.813 c-1.733-2.888-8.854-9.625-18.288-13.957c-5.583,8.085-15.4,14.534-30.801,19.442l-0.674-1.251 c16.459-6.737,26.661-15.304,30.512-24.736h-23.967c-2.021,0-4.909,0.385-6.449,0.962l-2.695-3.465 c2.311,0.192,4.813,0.385,7.026,0.385h26.854c2.405-7.315,3.561-14.919,3.85-26.758l7.219,3.368 c0.481,0.289,0.771,0.481,0.771,0.771c0,0.193-0.192,0.48-0.675,0.771l-2.694,1.829c-0.481,5.68-2.021,14.054-4.043,20.021h14.535 l4.428-5.583L354.286,372.196z M356.885,341.78c0.577,0.578,0.962,0.866,0.962,1.059c0,0.097-0.385,0.097-1.154,0.289 c-5.391,1.155-7.797,2.889-14.053,10.203l-1.251-0.77l6.738-11.261h-45.239c-2.021,0-4.909,0.288-6.449,0.865l-2.695-3.369 c2.311,0.289,4.813,0.386,7.026,0.386h20.501v-12.417h-12.417c-2.021,0-4.909,0.385-6.449,0.962l-2.695-3.465 c2.311,0.192,4.813,0.385,7.027,0.385h14.534v-5.486c0-3.754-0.097-6.063-0.289-9.433l6.93,2.983 c0.771,0.289,1.155,0.577,1.155,0.963c0,0.288-0.289,0.577-0.866,1.059l-2.31,1.925v7.989h16.17l4.043-4.909l5.486,5.679 c0.385,0.481,0.576,0.771,0.576,0.962c0,0.289-0.287,0.386-0.865,0.386h-25.411v12.417h21.08l3.562-3.946L356.885,341.78z M299.999,356.314c6.642,2.118,11.936,4.813,13.667,7.219c0.481,0.577,0.674,1.347,0.674,2.021c0,1.828-1.154,3.562-2.405,3.562 c-0.866,0-1.349-0.385-2.117-1.925c-1.54-3.08-5.103-6.737-10.396-9.914L299.999,356.314z M305.87,343.897 c6.064,1.829,11.55,4.909,12.994,7.219c0.385,0.674,0.578,1.444,0.578,2.214c0,1.829-1.06,3.465-2.118,3.465 c-0.866,0-1.347-0.578-2.118-2.021c-1.442-2.695-4.042-6.256-9.816-9.818L305.87,343.897z" />
<path
android:fillColor="#82A6F5"
android:pathData="M454.938,335.997c0.252,0.216,0.36,0.396,0.36,0.54c0,0.181-0.182,0.288-0.54,0.288h-14.688v5.688 c0,1.943-0.61,2.34-2.987,3.42c-0.071,0.036-0.145,0.071-0.18,0.071c-0.107,0-0.217-0.107-0.252-0.287 c-0.396-1.297-1.62-1.979-4.464-2.771l0.108-0.468c2.123,0.54,3.563,0.756,4.463,0.756c1.368,0,1.691-0.468,1.691-1.188v-5.219 h-12.419c-0.756,0-1.8,0.145-2.412,0.396l-1.008-1.332c0.9,0.071,1.8,0.144,2.628,0.144h13.211c0-1.188-0.036-2.556-0.071-3.204 l2.122,0.973l6.877-3.563h-18.286c-0.757,0-1.801,0.145-2.412,0.396l-1.008-1.332c0.899,0.071,1.8,0.144,2.628,0.144h19.582 l1.8-1.152l1.583,2.232c0.145,0.216,0.217,0.396,0.217,0.468c0,0.108-0.072,0.145-0.217,0.145c-0.107,0-0.539-0.036-0.647-0.036 c-1.728,0-4.787,1.116-10.547,3.671v1.26h10.728l1.619-2.231L454.938,335.997z M454.723,317.063 c0.252,0.216,0.359,0.396,0.359,0.54c0,0.144-0.181,0.252-0.504,0.252H426.43c-0.756,0-1.799,0.144-2.411,0.396l-1.008-1.332 c0.898,0.072,1.8,0.145,2.628,0.145h25.196l1.512-2.087L454.723,317.063z M449.359,320.699c0.216,0.145,0.324,0.216,0.324,0.36 c0,0.071-0.108,0.18-0.288,0.323l-0.937,0.685v1.872c0,1.224,0.035,1.979,0.107,2.879v0.144c0,0.612-1.151,0.9-1.476,0.9 c-0.325,0-0.324-0.108-0.324-1.296h-15.334c0,1.224,0,1.368-0.685,1.62c-0.359,0.144-0.611,0.216-0.792,0.216 c-0.252,0-0.323-0.145-0.323-0.54v-0.288c0.071-0.647,0.107-1.656,0.107-2.879v-2.7c0-0.899-0.036-1.908-0.107-2.592l1.979,1.332 h14.578l0.972-1.44L449.359,320.699z M446.768,325.774v-4.248h-15.334v4.248H446.768z M436.329,311.952 c3.349,1.152,4.355,2.052,4.355,3.384c0,0.9-0.504,1.62-1.08,1.62c-0.432,0-0.612-0.144-0.864-0.936 c-0.322-1.008-0.972-2.16-2.663-3.671L436.329,311.952z" />
<path
android:fillColor="#82A6F5"
android:pathData="M455.084,386.108c-1.872,0.18-2.593,0.504-3.276,1.439c-0.216,0.288-0.396,0.433-0.611,0.433 c-0.145,0-8.026-2.124-12.419-5.111c-4.031,3.023-9.719,4.896-15.838,5.867l-0.144-0.576c5.687-1.404,11.051-3.383,14.722-6.263 c-2.231-1.872-3.995-4.535-5.399-7.883c-1.332,0-1.69,0-2.734,0.288l-1.368-1.152c0.973,0.072,1.691,0.072,2.412,0.072h12.777 l1.188-1.152l2.268,1.764c0.252,0.181,0.36,0.324,0.36,0.396c0,0.107-0.145,0.18-0.504,0.288c-1.08,0.324-1.296,0.468-2.376,2.124 c-1.729,2.7-2.592,3.888-4.104,5.22c4.284,2.591,9.468,3.527,15.047,3.779V386.108L455.084,386.108z M454.723,368.867 c0.18,0.145,0.252,0.252,0.252,0.324c0,0.107-0.18,0.107-0.576,0.144c-1.655,0.108-3.022,1.584-4.967,3.672l-0.54-0.252 l2.556-3.995h-24.549c-0.252,2.771-0.685,3.995-1.62,4.606c-0.359,0.252-0.72,0.324-1.044,0.324c-0.504,0-1.116-0.252-1.116-0.937 c0-0.396,0.252-0.863,0.9-1.296c0.864-0.576,2.088-2.268,2.447-4.788l0.575,0.108c-0.035,0.432-0.035,0.828-0.071,1.188h15.37 c1.729-2.52,3.42-5.398,4.644-7.774l1.944,1.26c0.216,0.145,0.323,0.216,0.323,0.324s-0.253,0.216-0.827,0.396 c-0.324,0.108-0.828,0.612-1.548,1.513c-0.828,1.008-2.484,2.916-3.744,4.282h8.026l1.225-1.188L454.723,368.867z M450.763,358.32 c0.181,0.216,0.252,0.36,0.252,0.468c0,0.072-0.035,0.108-0.145,0.108c-0.108,0-0.252-0.036-0.468-0.108 c-0.359-0.107-1.044-0.252-1.728-0.252c-0.146,0-0.288,0.036-0.434,0.036c-5.614,0.54-14.47,1.08-22.893,1.332l0.036-0.504 c12.813-1.188,20.661-2.016,23.828-3.061L450.763,358.32z M429.635,361.344c0.646,0.359,4.392,2.411,4.392,4.139 c0,0.828-0.647,1.404-1.152,1.404c-0.322,0-0.72-0.324-0.826-0.937c-0.218-1.224-1.116-2.88-2.7-4.248L429.635,361.344z M432.945,374.014c1.548,2.986,3.601,5.326,5.688,6.946c2.016-1.836,3.384-3.852,4.787-6.946H432.945z M437.229,360.696 c2.736,1.656,3.708,2.988,3.815,3.6c0.035,0.145,0.035,0.252,0.035,0.396c0,1.044-0.898,1.62-1.366,1.62 c-0.434,0-0.576-0.288-0.757-1.548c-0.145-0.973-0.539-2.053-1.979-3.744L437.229,360.696z" />
<path
android:fillColor="#82A6F5"
android:pathData="M454.866,414.658c0.182,0.18,0.252,0.288,0.252,0.396c0,0.107-0.106,0.144-0.358,0.144h-28.868 c-0.721,0-1.765,0.145-2.304,0.36l-0.974-1.296c0.864,0.107,1.729,0.144,2.521,0.144h12.85v-4.284h-8.025 c-0.721,0-1.765,0.145-2.305,0.36l-0.973-1.295c0.828,0.107,1.729,0.144,2.521,0.144h8.782v-4.643h-9.719 c-0.756,0-1.836,0.144-2.411,0.359l-1.008-1.296c0.863,0.072,1.8,0.145,2.627,0.145h14.398c1.439-2.052,2.52-3.853,3.238-5.255 l1.98,1.296c0.216,0.144,0.324,0.252,0.324,0.324c0,0.107-0.146,0.18-0.396,0.252c-0.898,0.216-2.088,1.044-4.464,3.384h7.271 l1.225-1.765l2.34,2.017c0.18,0.18,0.288,0.288,0.288,0.396c0,0.108-0.145,0.145-0.433,0.145h-13.533v4.643h7.811l1.26-1.764 l2.017,1.979c0.181,0.181,0.288,0.324,0.288,0.396c0,0.144-0.145,0.18-0.396,0.18h-10.979v4.284h11.626l1.439-1.872 L454.866,414.658z M453.103,420.489c0.181,0.18,0.288,0.288,0.288,0.396c0,0.107-0.145,0.144-0.433,0.144h-14.541 c-1.729,5.579-5.651,8.963-15.228,11.123l-0.144-0.432c7.811-2.916,11.878-5.003,13.605-10.69h-8.963 c-0.721,0-1.765,0.144-2.304,0.36l-0.974-1.296c0.828,0.107,1.729,0.144,2.521,0.144h9.935c0.323-1.404,0.575-3.023,0.685-4.896 l2.412,1.224c0.18,0.107,0.286,0.216,0.286,0.324c0,0.071-0.07,0.18-0.252,0.252c-0.756,0.359-0.826,0.504-0.898,1.008 c-0.145,0.72-0.288,1.404-0.468,2.088h11.086l1.225-1.692L453.103,420.489z M431.073,398.784c2.16,0.432,4.032,1.188,4.788,2.196 c0.216,0.288,0.288,0.647,0.288,1.008c0,0.792-0.433,1.583-0.937,1.583c-0.324,0-0.576-0.108-0.863-0.972 c-0.288-0.9-1.692-2.593-3.456-3.492L431.073,398.784z M455.406,429.596c-1.512-0.072-2.556,0.432-3.312,1.548 c-0.107,0.18-0.216,0.252-0.359,0.252c-0.072,0-0.145-0.036-0.217-0.072c-5.327-2.124-9.034-5.076-11.626-10.295h0.684 c4.248,6.191,8.928,7.739,14.83,8.171V429.596L455.406,429.596z" />
<path
android:fillColor="#82A6F5"
android:pathData="M436.365,450.048c0.216,0.107,0.323,0.216,0.323,0.323c0,0.108-0.107,0.181-0.359,0.324l-1.115,0.576 c-0.359,5.759-1.296,10.475-3.023,14.074c2.88,1.728,4.355,3.06,4.355,4.68c0,1.008-0.612,1.404-0.973,1.404 c-0.288,0-0.54-0.252-0.937-0.937c-0.791-1.403-1.765-2.664-3.131-3.852c-2.053,3.456-4.824,6.119-9.287,8.351l-0.252-0.396 c3.814-2.448,6.623-5.507,8.387-8.927c-1.439-1.08-3.203-2.124-5.399-3.24c0.938-3.744,1.836-7.848,2.521-11.482l-1.835,0.072 c-0.756,0.036-1.8,0.288-2.376,0.576l-1.116-1.26c0.36,0,2.196-0.036,2.628-0.036l2.844-0.108c0.685-3.563,1.151-6.587,1.296-8.314 l2.483,1.512c0.145,0.072,0.216,0.144,0.216,0.252c0,0.072-0.071,0.144-0.18,0.216l-0.9,0.612c-0.396,1.8-0.827,3.672-1.295,5.688 l3.743-0.144l1.224-1.477L436.365,450.048z M429.058,450.911c-0.756,3.349-1.619,7.092-2.663,11.52 c1.655,0.684,3.167,1.403,4.463,2.124c1.477-3.348,2.34-8.027,2.556-13.858L429.058,450.911z M455.118,458.255 c0.36,0.323,0.504,0.54,0.504,0.684c0,0.18-0.216,0.252-0.646,0.252h-8.783v12.203c0,2.124-0.144,2.304-2.699,3.815 c-0.071,0.035-0.145,0.071-0.216,0.071c-0.107,0-0.216-0.107-0.252-0.288c-0.252-1.26-1.044-1.62-4.536-2.664l0.108-0.504 c2.16,0.396,3.563,0.612,4.428,0.612c1.403,0,1.477-0.576,1.477-1.872V459.19h-5.509c-0.756,0-1.8,0.144-2.411,0.396l-1.008-1.332 c0.899,0.071,1.8,0.144,2.628,0.144h6.3v-2.664c0-1.296-0.036-3.131-0.072-3.887l1.764,0.936l4.032-6.371h-9.791 c-0.756,0-1.8,0.145-2.412,0.396l-1.008-1.332c0.899,0.072,1.8,0.144,2.628,0.144h10.511l1.26-1.512l2.195,2.124 c0.181,0.18,0.252,0.288,0.252,0.396c0,0.144-0.144,0.18-0.432,0.216c-1.188,0.107-2.195,0.72-6.769,6.19 c0.182,0.072,0.612,0.324,0.612,0.433c0,0.107-0.108,0.18-1.08,0.684v4.248h5.508l1.513-2.016L455.118,458.255z" />
<path
android:fillColor="#82A6F5"
android:pathData="M435.645,489.288c0.181,0.145,0.288,0.217,0.288,0.324c0,0.072-0.107,0.181-0.288,0.288l-1.188,0.828 v15.046c0,2.736,0.036,4.067,0.108,5.219v0.216c0,0.757-1.512,1.477-1.548,1.477c-0.146,0-0.181-0.252-0.181-0.72v-1.656h-6.911 v3.06c0,0.396-0.071,0.469-0.756,0.9c-0.359,0.216-0.575,0.36-0.721,0.36c-0.216,0-0.252-0.253-0.252-0.828v-0.36 c0.072-2.232,0.108-4.679,0.108-7.523v-14.325c0-1.8-0.036-2.809-0.108-4.032l2.16,1.584h6.119l0.936-1.296L435.645,489.288z M432.838,498.755v-8.819h-6.911v8.819H432.838z M432.838,509.517v-9.971h-6.911v9.971H432.838z M455.19,494.399 c0.18,0.18,0.288,0.324,0.288,0.396c0,0.107-0.145,0.144-0.468,0.144h-5.508v19.833c0,2.339-0.036,2.483-2.735,3.635 c-0.145,0.036-0.216,0.072-0.324,0.072c-0.144,0-0.216-0.072-0.252-0.288c-0.288-1.332-0.972-1.944-5.219-3.06l0.106-0.576 c2.376,0.504,3.96,0.756,4.969,0.756c1.476,0,1.8-0.468,1.8-1.116v-19.257H439.1c-0.647,0-1.512,0.108-2.124,0.252l-1.296-1.188 c0.899,0.107,1.691,0.144,2.628,0.144h9.539v-3.814c0-2.988,0-3.996-0.072-5.221l2.556,1.296c0.253,0.145,0.396,0.216,0.396,0.324 s-0.146,0.216-0.396,0.36l-0.827,0.468v6.587h2.268l1.296-2.016L455.19,494.399z M438.849,498.646 c2.699,2.087,4.464,3.671,4.464,5.399c0,0.899-0.54,1.764-1.261,1.764c-0.468,0-0.685-0.252-0.973-1.692 c-0.287-1.512-0.972-3.275-2.592-5.184L438.849,498.646z" />
<path
android:fillColor="#82A6F5"
android:pathData="M453.535,550.342c0,4.176,0.396,5.398,1.477,5.976c0.18,0.108,0.288,0.18,0.288,0.324 c0,0.072-0.036,0.144-0.072,0.252c-1.044,2.16-1.584,2.484-3.779,2.736c-1.439,0.144-3.312,0.252-4.858,0.252 c-5.04,0-5.688-0.721-5.688-3.313V543.61h-5.508c-0.144,9.646-1.26,13.786-12.13,18.034l-0.252-0.468 c8.963-4.716,10.689-7.415,10.689-17.566h-7.307c-0.756,0-1.8,0.145-2.412,0.396l-1.008-1.332c0.898,0.071,1.8,0.144,2.628,0.144 h12.599v-9.395c0-1.477-0.071-3.924-0.145-5.256l2.628,1.477c0.216,0.107,0.324,0.252,0.324,0.432c0,0.108-0.072,0.252-0.218,0.36 l-0.898,0.792v11.59h1.584c1.8-2.412,5.075-7.667,6.659-10.941l2.34,1.764c0.106,0.107,0.18,0.18,0.18,0.252 c0,0.107-0.107,0.18-0.324,0.216c-0.684,0.144-1.188,0.54-1.584,1.008l-6.335,7.703h8.388l1.62-2.196l2.376,2.196 c0.252,0.216,0.358,0.396,0.358,0.54c0,0.145-0.18,0.252-0.575,0.252h-11.986v12.778c0,1.728,1.08,1.872,2.808,1.872 c1.44,0,4.212-0.108,5.185-0.216c1.691-0.217,2.376-0.685,2.376-7.703L453.535,550.342L453.535,550.342z M427.978,532.271 c3.492,2.088,5.939,4.716,5.939,5.651c0,1.403-0.433,2.411-1.225,2.411c-0.359,0-0.612-0.252-0.864-1.188 c-0.396-1.44-1.942-4.68-4.139-6.443L427.978,532.271z" />
<path
android:fillColor="#82A6F5"
android:pathData="M336.627,1009.641h1.169v0.174h-1.169V1009.641z" />
<path
android:fillColor="#82A6F5"
android:pathData="M323.57,1077.314l0.672,0.24c-1.651-1.31-5.873-4.357-11.369-6.172c-6.539-2.156-16.283-2.948-25.559,5.512 c-0.105,0.098-0.236,0.131-0.383,0.092c-2.219-0.543-11.82-24.271-20.374-46.504c-0.464,0.313-0.917,0.67-1.389,1.039 c-0.831,0.659-1.731,1.363-2.839,1.929c10.691,26.719,21.417,52.866,22.988,54.386 C295.088,1067.123,308.29,1071.844,323.57,1077.314z" />
<path
android:fillColor="#82A6F5"
android:pathData="M387.494,1076.688c-0.146,0.031-0.278,0-0.383-0.098c-9.277-8.462-19.022-7.668-25.567-5.515 c-5.47,1.803-9.669,4.826-11.339,6.144l0.064-0.025c14.236-5.228,28.957-10.629,38.836,10.312 c1.525-1.474,11.693-26.187,22.068-52.094c-0.115-0.052-0.228-0.097-0.33-0.126c-1.754-0.471-3.011-1.342-4.072-2.237 C398.51,1054.402,389.617,1076.162,387.494,1076.688z" />
<path
android:fillColor="#82A6F5"
android:pathData="M335.92,1009.988l-0.053-0.061c-3.911-4.523-39.396-43.937-80.371-34.611 c-0.289,0.447,1.828,4.865,4.064,8.907l7.104-0.201l-3.673-6.462l0.247-0.039c23.855-3.857,63.73,25.469,71.48,31.385l-0.19,0.271 c0.216,0.123,0.427,0.246,0.633,0.37l0.344,0.202l-0.382,0.112c-0.129,0.037-0.264,0.225-0.264,0.494l2.018,66.188 c0,0.305,0.166,0.524,0.315,0.524h0.363c0.146,0,0.314-0.221,0.314-0.52l2.019-66.123c0-0.254-0.137-0.439-0.318-0.439h-0.811 l0.522-0.321c8.939-5.455,27.154-11.722,46.41-15.962c15.455-3.405,33.166-5.711,36.504-1.967l0.066,0.077l-0.031,0.098 c-0.393,1.073-3.553,9.711-7.83,21.094c1.662,0.418,3.478,0.954,5.074,1.563c4.443-11.213,8.469-21.466,11.311-28.748 c-43.979-4.454-80.327,16.482-90.432,23.016l-0.201-0.284c10.863-8.298,48.09-35.085,71.033-31.375l0.246,0.039l-3.674,6.453 l7.104,0.186c2.24-4.024,4.354-8.428,4.051-8.887c-40.827-9.281-76.438,30.404-80.362,34.959l-0.054,0.061h-0.317v-0.174h-0.384 h-1.169h-0.382v0.174H335.92L335.92,1009.988z" />
<path
android:fillColor="#82A6F5"
android:pathData="M252.195,992.182l-0.036-0.097l0.07-0.077c3.323-3.729,21.045-1.468,36.524,1.891 c18.602,4.035,36.266,9.92,45.519,15.133c-10.467-6.72-46.691-27.325-90.633-22.875c2.65,6.793,6.331,16.165,10.419,26.495 c1.791-0.464,3.706-0.826,5.328-1.065C255.435,1001.046,252.567,993.201,252.195,992.182z" />
<path
android:fillColor="#82A6F5"
android:pathData="M248.686,1016.834c-0.151,0.89,0.351,1.643,0.664,2.013c0.76,0.898,1.789,1.256,2.124,1.045 c2.864-1.798,7.438-3.373,13.588-4.676c0.74-0.158,2.808-0.822,2.62-1.787c-0.25-1.268-2.175-1.531-3.745-1.531 c-0.503,0-0.939,0.027-1.201,0.044c-0.102,0.007-0.18,0.011-0.23,0.013c-2.323,0.082-8.587,1.109-11.841,2.552 C249.854,1014.862,248.837,1015.934,248.686,1016.834z" />
<path
android:fillColor="#82A6F5"
android:pathData="M250.244,1023.291c0.381,1.347,1.087,2.028,2.096,2.028c0.705,0,1.309-0.335,1.478-0.437l0.156-0.097 c1.4-0.856,5.668-3.471,12.262-4.232c2.932-0.339,6.361-1.8,6.225-3.106c-0.045-0.441-0.185-1.783-3.015-1.783 c-1.49,0-3.229,0.354-4.731,0.698c-3.586,0.825-9.587,2.663-12.804,4.087C251.168,1020.777,249.874,1021.985,250.244,1023.291z" />
<path
android:fillColor="#82A6F5"
android:pathData="M268.087,1021.346c-0.074,0.01-0.14,0.016-0.189,0.022c-2.99,0.278-8.495,1.687-13.512,4.119 c-0.778,0.378-1.654,1.358-1.712,2.273c-0.062,1.046,0.34,1.709,0.688,2.078c0.526,0.559,1.183,0.757,1.623,0.757 c0.143,0,0.27-0.021,0.377-0.06c2.138-0.757,3.664-1.619,5.139-2.458c1.357-0.766,2.759-1.563,4.587-2.229 c0.065-0.021,0.231-0.068,0.47-0.131c4.775-1.277,5.376-2.258,5.31-2.729c-0.166-1.162-0.803-1.703-2.009-1.703 C268.558,1021.287,268.285,1021.32,268.087,1021.346z" />
<path
android:fillColor="#82A6F5"
android:pathData="M264.754,1027.191l-0.093,0.032c-2.167,0.767-3.479,1.604-6.098,3.271c-0.455,0.289-0.945,0.604-1.485,0.944 c-0.376,0.237-0.594,0.497-0.65,0.77c-0.055,0.268,0.036,0.571,0.271,0.916c0.609,0.881,1.08,0.979,1.38,0.979l0,0 c0.313,0,0.633-0.129,0.943-0.252c0.228-0.092,0.463-0.188,0.7-0.234c2.219-0.429,3.641-1.552,5.018-2.64 c0.624-0.493,1.212-0.958,1.862-1.354c0.039-0.021,0.089-0.053,0.15-0.086c0.451-0.26,1.652-0.94,1.813-1.784 c0.064-0.339-0.042-0.677-0.328-1.032c-0.099-0.124-0.344-0.192-0.69-0.192C266.588,1026.529,265.207,1027.029,264.754,1027.191z" />
<path
android:fillColor="#82A6F5"
android:pathData="M409.591,1012.775c-0.422-0.059-1.201-0.166-2.06-0.166c-1.201,0-2.692,0.225-2.985,1.289 c-0.261,0.949,1.756,1.763,2.483,1.973c6.041,1.744,10.486,3.645,13.217,5.645c0.07,0.056,0.183,0.083,0.324,0.083 c0.492,0,1.32-0.365,1.953-1.063c0.527-0.578,0.779-1.239,0.721-1.865c-0.086-0.908-1.024-2.052-1.805-2.468 c-3.143-1.674-9.311-3.149-11.625-3.397C409.766,1012.798,409.688,1012.788,409.591,1012.775z" />
<path
android:fillColor="#82A6F5"
android:pathData="M419.768,1022.044c-3.107-1.652-8.957-3.922-12.478-5.003c-1.706-0.522-3.661-1.059-5.237-1.059 c-2.047,0-2.441,0.9-2.566,1.58c-0.227,1.293,3.086,2.999,5.988,3.548c6.516,1.237,10.582,4.149,11.92,5.106l0.15,0.11 c0.182,0.126,0.829,0.545,1.596,0.545c0.93,0,1.631-0.63,2.086-1.879C421.688,1023.721,420.486,1022.425,419.768,1022.044z" />
<path
android:fillColor="#82A6F5"
android:pathData="M416.934,1026.889c-4.83-2.79-10.217-4.592-13.18-5.086c-0.05-0.01-0.111-0.021-0.185-0.035 c-0.25-0.051-0.623-0.125-1.018-0.125c-1.033,0-1.646,0.512-1.877,1.563c-0.099,0.464,0.428,1.486,5.104,3.104 c0.232,0.082,0.394,0.137,0.453,0.166c1.777,0.799,3.117,1.691,4.416,2.559c1.408,0.941,2.869,1.912,4.947,2.818 c0.137,0.059,0.303,0.09,0.487,0.09c0.98,0,2.369-0.824,2.394-2.662C418.486,1028.359,417.684,1027.32,416.934,1026.889z" />
<path
android:fillColor="#82A6F5"
android:pathData="M406.559,1027.88l-0.092-0.039c-1.291-0.578-2.271-0.869-2.902-0.869c-0.182,0-0.422,0.024-0.535,0.147 c-0.309,0.338-0.44,0.666-0.401,1.008c0.1,0.851,1.25,1.619,1.678,1.908c0.059,0.039,0.11,0.071,0.146,0.099 c0.619,0.44,1.172,0.946,1.761,1.485c1.295,1.183,2.633,2.406,4.813,2.993c0.234,0.065,0.461,0.175,0.682,0.282 c0.324,0.16,0.662,0.328,1,0.328c0.441,0,0.89-0.292,1.369-0.887c0.259-0.324,0.371-0.626,0.338-0.894 c-0.038-0.274-0.235-0.551-0.594-0.811c-0.514-0.381-0.977-0.729-1.406-1.046C409.923,1029.73,408.669,1028.796,406.559,1027.88z" />
</vector>

@ -8,7 +8,7 @@
android:id="@+id/bottom_navigation_view" android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:elevation="6dp" android:elevation="10dp"
android:background="@color/background" android:background="@color/background"
app:labelVisibilityMode="labeled" app:labelVisibilityMode="labeled"
app:menu="@menu/main_bnv" app:menu="@menu/main_bnv"

@ -10,7 +10,7 @@
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<WebView <WebView
android:id="@+id/webView" android:id="@+id/web_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

@ -2,15 +2,17 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"> android:orientation="vertical">
<io.legado.app.ui.widget.TitleBar <io.legado.app.ui.widget.TitleBar
android:id="@+id/title_bar" android:id="@+id/title_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
app:title="@string/login" />
<WebView <WebView
android:id="@+id/webView" android:id="@+id/web_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

@ -1,15 +1,71 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root_view"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:gravity="center_horizontal">
<ImageView <View
android:id="@+id/iv_bg" android:id="@+id/vw_title_line"
android:layout_width="6dp"
android:layout_height="0dp"
android:background="@color/colorAccent"
app:layout_constraintTop_toTopOf="@+id/tv_title"
app:layout_constraintBottom_toBottomOf="@+id/tv_title"
app:layout_constraintLeft_toLeftOf="@id/iv_book" />
<io.legado.app.ui.widget.text.AccentTextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="1"
android:text="阅读"
android:textSize="49sp"
app:layout_constraintVertical_bias="0.3"
app:layout_constraintRight_toLeftOf="@+id/tv_sub_title"
app:layout_constraintLeft_toRightOf="@+id/vw_title_line"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/iv_book"
tools:ignore="HardcodedText" />
<io.legado.app.ui.widget.text.AccentTextView
android:id="@+id/tv_sub_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:src="@drawable/image_welcome" android:layout_marginTop="60dp"
android:ems="1"
android:text="享受美好时光"
android:textSize="16sp"
app:layout_constraintTop_toTopOf="@+id/tv_title"
app:layout_constraintLeft_toRightOf="@id/tv_title"
app:layout_constraintRight_toRightOf="@id/iv_book"
tools:ignore="HardcodedText" />
<ImageView
android:id="@+id/iv_book"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginBottom="32dp"
android:src="@drawable/icon_read_book"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:contentDescription="@string/welcome" /> android:contentDescription="@string/welcome"
app:layout_constraintBottom_toTopOf="@+id/tv_gzh"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<io.legado.app.ui.widget.text.AccentTextView
android:id="@+id/tv_gzh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="关注公众号[开源阅读软件]\n看文章点广告支持作者"
android:layout_marginBottom="32dp"
android:gravity="center_horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
tools:ignore="HardcodedText" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:id="@+id/ll_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintRight_toLeftOf="@+id/ll_sort"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent">
<io.legado.app.ui.widget.text.AccentTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp"
android:text="@string/view"
android:textSize="16sp" />
<RadioGroup
android:id="@+id/rg_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/layout_list" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/layout_grid3" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/layout_grid4" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/layout_grid5" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/layout_grid6" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/ll_sort"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintLeft_toRightOf="@+id/ll_layout"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent">
<io.legado.app.ui.widget.text.AccentTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp"
android:text="@string/sort"
android:textSize="16sp" />
<RadioGroup
android:id="@+id/rg_sort"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/bookshelf_px_0" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/bookshelf_px_1" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/bookshelf_px_2" />
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/bookshelf_px_3" />
</RadioGroup>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -7,6 +7,7 @@
android:id="@+id/number_picker" android:id="@+id/number_picker"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" /> android:layout_gravity="center"
android:scrollbars="none" />
</FrameLayout> </FrameLayout>

@ -147,6 +147,7 @@
<!--底部设置栏--> <!--底部设置栏-->
<FrameLayout <FrameLayout
android:id="@+id/fl_bottom_bg"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="100dp"> android:layout_height="100dp">

@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/action_scan" android:id="@+id/menu_scan"
android:icon="@drawable/ic_scan" android:icon="@drawable/ic_scan"
android:title="@string/scan_qr_code" android:title="@string/scan_qr_code"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_success"
android:icon="@drawable/ic_check"
android:title="@string/success"
app:showAsAction="always" />
</menu>

@ -35,14 +35,6 @@
<item>9</item> <item>9</item>
</string-array> </string-array>
<string-array name="bookshelf_layout">
<item>@string/layout_list</item>
<item>@string/layout_grid3</item>
<item>@string/layout_grid4</item>
<item>@string/layout_grid5</item>
<item>@string/layout_grid6</item>
</string-array>
<string-array name="indent"> <string-array name="indent">
<item>@string/indent_0</item> <item>@string/indent_0</item>
<item>@string/indent_1</item> <item>@string/indent_1</item>
@ -111,18 +103,6 @@
<item>@string/screen_sensor</item> <item>@string/screen_sensor</item>
</string-array> </string-array>
<string-array name="bookshelf_px_title">
<item>@string/bookshelf_px_0</item>
<item>@string/bookshelf_px_1</item>
<item>@string/bookshelf_px_2</item>
</string-array>
<string-array name="bookshelf_px_value">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
<declare-styleable name="Battery"> <declare-styleable name="Battery">
<attr name="batteryOrientation"> <attr name="batteryOrientation">
<enum name="horizontal" value="0" /> <enum name="horizontal" value="0" />

@ -15,7 +15,7 @@
<color name="background">@color/md_grey_50</color> <color name="background">@color/md_grey_50</color>
<color name="background_card">@color/md_grey_100</color> <color name="background_card">@color/md_grey_100</color>
<color name="background_menu">@color/md_grey_300</color> <color name="background_menu">@color/md_grey_200</color>
<color name="transparent">#00000000</color> <color name="transparent">#00000000</color>
<color name="transparent30">#30000000</color> <color name="transparent30">#30000000</color>

@ -47,12 +47,13 @@
<string name="bookshelf_empty">书架还空着,先去添加吧!</string> <string name="bookshelf_empty">书架还空着,先去添加吧!</string>
<string name="action_search">搜索</string> <string name="action_search">搜索</string>
<string name="action_download">下载</string> <string name="action_download">下载</string>
<string name="layout_list">列表视图</string> <string name="layout_list">列表</string>
<string name="layout_grid3">网格视图三列</string> <string name="layout_grid3">网格三列</string>
<string name="layout_grid4">网格视图四列</string> <string name="layout_grid4">网格四列</string>
<string name="layout_grid5">网格视图五列</string> <string name="layout_grid5">网格五列</string>
<string name="layout_grid6">网格视图六列</string> <string name="layout_grid6">网格六列</string>
<string name="bookshelf_layout">书架布局</string> <string name="bookshelf_layout">书架布局</string>
<string name="view">视图</string>
<string name="book_library">书城</string> <string name="book_library">书城</string>
<string name="book_local">添加本地</string> <string name="book_local">添加本地</string>
<string name="book_source">书源</string> <string name="book_source">书源</string>
@ -192,7 +193,7 @@
<string name="img_cover">封面</string> <string name="img_cover">封面</string>
<string name="book"></string> <string name="book"></string>
<string name="volume_key_page">音量键翻页</string> <string name="volume_key_page">音量键翻页</string>
<string name="click_open_page">点击翻页</string> <string name="click_turn_page">点击翻页</string>
<string name="click_all_next_page">点击总是翻下一页</string> <string name="click_all_next_page">点击总是翻下一页</string>
<string name="page_anim">翻页动画</string> <string name="page_anim">翻页动画</string>
<string name="keep_light">屏幕超时</string> <string name="keep_light">屏幕超时</string>
@ -226,11 +227,11 @@
<string name="origin_show">来源:%s</string> <string name="origin_show">来源:%s</string>
<string name="import_replace_rule">本地导入</string> <string name="import_replace_rule">本地导入</string>
<string name="import_replace_rule_on_line">网络导入</string> <string name="import_replace_rule_on_line">网络导入</string>
<string name="bookshelf_px">书架排序</string>
<string name="check_update_interval">检查更新间隔</string> <string name="check_update_interval">检查更新间隔</string>
<string name="bookshelf_px_0">按阅读时间排序</string> <string name="bookshelf_px_0">按阅读时间</string>
<string name="bookshelf_px_1">按更新时间排序</string> <string name="bookshelf_px_1">按更新时间</string>
<string name="bookshelf_px_2">手动排序</string> <string name="bookshelf_px_2">按书名</string>
<string name="bookshelf_px_3">手动排序</string>
<string name="read_type">阅读方式</string> <string name="read_type">阅读方式</string>
<string name="del_select">删除所选</string> <string name="del_select">删除所选</string>
<string name="del_msg">是否确认删除?</string> <string name="del_msg">是否确认删除?</string>
@ -487,6 +488,7 @@
<string name="indent_3">三字符缩进</string> <string name="indent_3">三字符缩进</string>
<string name="indent_4">四字符缩进</string> <string name="indent_4">四字符缩进</string>
<string name="select_folder">选择文件夹</string> <string name="select_folder">选择文件夹</string>
<string name="select_file">选择文件</string>
<string name="no_find">没有发现,可以在书源里添加。</string> <string name="no_find">没有发现,可以在书源里添加。</string>
<string name="restore_default">恢复默认</string> <string name="restore_default">恢复默认</string>
<string name="set_download_per">自定义缓存路径需要存储权限</string> <string name="set_download_per">自定义缓存路径需要存储权限</string>

@ -28,6 +28,12 @@
android:key="volumeKeyPage" android:key="volumeKeyPage"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.SwitchPreference
android:defaultValue="true"
android:title="@string/click_turn_page"
android:key="clickTurnPage"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.SwitchPreference <io.legado.app.ui.widget.prefs.SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:title="@string/click_all_next_page" android:title="@string/click_all_next_page"

@ -60,6 +60,14 @@
app:cpv_dialogType="preset" app:cpv_dialogType="preset"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.ColorPreference
android:defaultValue="@color/md_grey_200"
android:key="colorBottomBackground"
android:summary="白天,底栏色"
android:title="底部操作栏颜色"
app:cpv_dialogType="preset"
app:iconSpaceReserved="false" />
</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
@ -90,6 +98,14 @@
app:cpv_dialogType="preset" app:cpv_dialogType="preset"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.ColorPreference
android:defaultValue="@color/md_grey_800"
android:key="colorBottomBackgroundNight"
android:summary="夜间,底栏色"
android:title="底部操作栏颜色"
app:cpv_dialogType="preset"
app:iconSpaceReserved="false" />
</io.legado.app.ui.widget.prefs.PreferenceCategory> </io.legado.app.ui.widget.prefs.PreferenceCategory>
</androidx.preference.PreferenceScreen> </androidx.preference.PreferenceScreen>
Loading…
Cancel
Save