Merge pull request #2 from gedoor/master

update
pull/123/head
52fisher 5 years ago committed by GitHub
commit 1c392c567e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  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. 25
      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**
* 添加书源一键导入
* 修复主题模式跟随系统
* 修复书源校验
* 添加书架排序
* 添加点击翻页开关
* 修复共用布局没有记住配置的bug
**2020/02/28**
* 解决阅读界面部分字体超出范围的问题

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

@ -22,7 +22,7 @@ object AppConst {
const val UA_NAME = "User-Agent"
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 {

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

@ -237,14 +237,30 @@ object BookHelp {
private var bookName: String? = null
private var bookOrigin: String? = null
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(
title: String,
@ -253,23 +269,17 @@ object BookHelp {
content: String,
enableReplace: Boolean
): 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
for (item in replaceRules) {
item.pattern.let {
if (it.isNotEmpty()) {
c = if (item.isRegex) {
c.replace(it.toRegex(), item.replacement)
} else {
c.replace(it, item.replacement)
if (enableReplace) {
upReplaceRules(name, origin)
for (item in replaceRules) {
item.pattern.let {
if (it.isNotEmpty()) {
c = if (item.isRegex) {
c.replace(it.toRegex(), item.replacement)
} else {
c.replace(it, item.replacement)
}
}
}
}
@ -281,6 +291,6 @@ object BookHelp {
1 -> c = ZhConvertBootstrap.newInstance().toSimple(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? {
return try {
val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, null, null)
val call = analyzeUrl.getResponse()
val call = analyzeUrl.getResponse(urlStr)
val response = call.execute()
response.body()
} catch (e: Exception) {

@ -26,23 +26,6 @@ object ReadBookConfig {
}
val durConfig get() = getConfig(styleSelect)
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
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 hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar)
var textBold: Boolean

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

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

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

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

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

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

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

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

@ -147,6 +147,11 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
return this
}
override fun bottomBackground(color: Int): ThemeStore {
mEditor.putInt(ThemeStorePrefKeys.KEY_BOTTOM_BACKGROUND, color)
return this
}
override fun coloredStatusBar(colored: Boolean): ThemeStore {
mEditor.putBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, colored)
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
fun coloredStatusBar(context: Context): Boolean {
return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, true)

@ -78,8 +78,12 @@ internal interface ThemeStoreInterface {
fun textColorSecondaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore
// Background
fun backgroundColor(@ColorInt color: Int): ThemeStore
fun bottomBackground(@ColorInt color: Int): ThemeStore
// Toggle configurations
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_BACKGROUND_COLOR = "backgroundColor"
const val KEY_BOTTOM_BACKGROUND = "bottomBackground"
const val KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar"
const val KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar"

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

@ -37,7 +37,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap()
)
val res = analyzeUrl.getResponseAwait()
val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl)
BookList.analyzeBookList(
res.body,
bookSource,
@ -65,7 +65,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap()
)
val res = analyzeUrl.getResponseAwait()
val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl)
BookList.analyzeBookList(
res.body,
bookSource,
@ -96,7 +96,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap()
)
analyzeUrl.getResponseAwait().body
analyzeUrl.getResponseAwait(bookSource.bookSourceUrl).body
}
BookInfo.analyzeBookInfo(book, body, bookSource, book.bookUrl)
book
@ -122,7 +122,7 @@ class WebBook(val bookSource: BookSource) {
ruleUrl = book.tocUrl,
baseUrl = book.bookUrl,
headerMapF = bookSource.getHeaderMap()
).getResponseAwait().body
).getResponseAwait(bookSource.bookSourceUrl).body
}
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? {
return try {
val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, baseUrl, book)
val call = analyzeUrl.getResponse()
val call = analyzeUrl.getResponse(urlStr)
val response = call.execute()
response.body()
} 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.data.entities.BaseBook
import io.legado.app.help.JsExtensions
import io.legado.app.help.http.AjaxWebView
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.*
import io.legado.app.help.http.api.HttpGetApi
import io.legado.app.help.http.api.HttpPostApi
import io.legado.app.utils.*
@ -243,7 +240,11 @@ class AnalyzeUrl(
}
@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 {
method == RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) {
@ -267,7 +268,7 @@ class AnalyzeUrl(
@Throws(Exception::class)
suspend fun getResponseAwait(
tag: String? = null,
tag: String,
jsStr: String? = null,
sourceRegex: String? = null
): Res {
@ -278,8 +279,13 @@ class AnalyzeUrl(
params.javaScript = jsStr
params.sourceRegex = sourceRegex
params.postData = bodyTxt?.toByteArray()
params.tag = tag
return HttpHelper.ajax(params)
}
val cookie = CookieStore.getCookie(tag)
if (cookie.isNotEmpty()) {
headerMap["Cookie"] = cookie
}
val res = when {
method == RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) {

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

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

@ -20,7 +20,9 @@ import io.legado.app.constant.PreferKey
import io.legado.app.constant.Status
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp
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.noButton
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.changesource.ChangeSourceDialog
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.edit.ReplaceEditDialog
import io.legado.app.ui.widget.dialog.TextDialog
@ -63,6 +66,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
ChangeSourceDialog.CallBack,
ReadBook.CallBack,
TocRegexDialog.CallBack,
ReplaceEditDialog.CallBack,
ColorPickerDialogListener {
private val requestCodeChapterList = 568
private val requestCodeEditSource = 111
@ -219,6 +223,12 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
supportFragmentManager,
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)
}
@ -544,14 +554,33 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
}
}
/**
* 替换规则变化
*/
override fun onReplaceRuleSave() {
Coroutine.async {
BookHelp.upReplaceRules()
ReadBook.loadContent()
}
}
/**
* 显示阅读样式配置
*/
override fun showReadStyle() {
ReadStyleDialog().show(supportFragmentManager, "readStyle")
}
/**
* 显示更多设置
*/
override fun showMoreSetting() {
MoreConfigDialog().show(supportFragmentManager, "moreConfig")
}
/**
* 更新状态栏,导航栏
*/
override fun upSystemUiVisibility() {
Help.upSystemUiVisibility(this, !read_menu.isVisible)
}
@ -610,7 +639,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
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.help.AppConfig
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.buttonDisabledColor
import io.legado.app.service.help.ReadBook
@ -48,6 +49,7 @@ class ReadMenu : FrameLayout {
fabNightTheme.setImageResource(R.drawable.ic_brightness)
}
initAnimation()
ATH.applyBackgroundTint(fl_bottom_bg)
vw_bg.onClick { }
vwNavigationBar.onClick { }
seek_brightness.progress = context.getPrefInt("brightness", 100)

@ -95,6 +95,7 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
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)

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

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

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

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

@ -120,7 +120,7 @@ class PageView(context: Context, attrs: AttributeSet) :
if (noAnim) {
fillPage(PageDelegate.Direction.PREV)
} else {
pageDelegate?.start(PageDelegate.Direction.PREV)
pageDelegate?.prevPageByAnim()
}
}
@ -128,7 +128,7 @@ class PageView(context: Context, attrs: AttributeSet) :
if (noAnim) {
fillPage(PageDelegate.Direction.NEXT)
} 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
}
override fun hasNextPlus(): Boolean = with(dataSource) {
return hasNextChapter() || pageIndex < (currentChapter?.pageSize() ?: 1) - 2
}
override fun moveToFirst() {
ReadBook.setPageIndex(0)
}
@ -68,6 +72,9 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
?: TextPage(title = it.title).format()
}
}
if (!hasNextChapter()) {
return@with TextPage(text = "")
}
nextChapter?.let {
return@with it.page(0)?.removePageAloudSpan()
?: 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 com.google.android.material.snackbar.Snackbar
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.PageView
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 onDraw(canvas: Canvas) {}//绘制
@ -193,6 +166,10 @@ abstract class PageDelegate(protected val pageView: PageView) :
open fun onScroll() {}//移动contentView, slidePage
abstract fun nextPageByAnim()
abstract fun prevPageByAnim()
@CallSuper
open fun setDirection(direction: Direction) {
mDirection = direction
@ -260,21 +237,14 @@ abstract class PageDelegate(protected val pageView: PageView) :
if (centerRectF.contains(x, y)) {
pageView.callBack.clickCenter()
setTouchPoint(x, y)
} else {
} else if (ReadBookConfig.clickTurnPage) {
if (x > viewWidth / 2 ||
AppConfig.clickAllNext
) {
//设置动画方向
if (!hasNext()) return true
setDirection(Direction.NEXT)
setBitmap()
nextPageByAnim()
} else {
if (!hasPrev()) return true
setDirection(Direction.PREV)
setBitmap()
prevPageByAnim()
}
setTouchPoint(x, y)
onAnimStart()
}
return true
}

@ -2,6 +2,7 @@ package io.legado.app.ui.book.read.page.delegate
import android.view.MotionEvent
import android.view.VelocityTracker
import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.ui.book.read.page.PageView
import kotlin.math.abs
@ -58,4 +59,12 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
super.onDestroy()
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 {
when (item.itemId) {
R.id.action_scan -> {
R.id.menu_scan -> {
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.theme.ATH
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.utils.GSON
import io.legado.app.utils.applyTint
@ -67,22 +68,20 @@ class BookSourceEditActivity :
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_save -> {
val source = getSource()
R.id.menu_save -> getSource().let { source ->
if (checkSource(source)) {
viewModel.save(source) { setResult(Activity.RESULT_OK); finish() }
}
}
R.id.menu_debug_source -> {
val source = getSource()
R.id.menu_debug_source -> getSource().let { source ->
if (checkSource(source)) {
viewModel.save(source) {
startActivity<BookSourceDebugActivity>(Pair("key", source.bookSourceUrl))
}
}
}
R.id.menu_copy_source -> {
GSON.toJson(getSource())?.let { sourceStr ->
R.id.menu_copy_source -> getSource().let { source ->
GSON.toJson(source)?.let { sourceStr ->
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
clipboard?.setPrimaryClip(ClipData.newPlainText(null, sourceStr))
}
@ -101,6 +100,18 @@ class BookSourceEditActivity :
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)
}

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.SubMenu
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil
@ -20,14 +21,13 @@ import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.BookSource
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.theme.ATH
import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.service.help.CheckSource
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
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.widget.SelectActionBar
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.startActivityForResult
import org.jetbrains.anko.toast
import java.io.FileNotFoundException
import java.io.File
class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity_book_source),
PopupMenu.OnMenuItemClickListener,
@ -50,7 +50,8 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
get() = getViewModel(BookSourceViewModel::class.java)
private val importRecordKey = "bookSourceRecordKey"
private val qrRequestCode = 101
private val importSource = 132
private val importRequestCode = 132
private val exportRequestCode = 65
private lateinit var adapter: BookSourceAdapter
private var bookSourceLiveDate: LiveData<List<BookSource>>? = null
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_group_manage ->
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()
}
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_enable_explore -> viewModel.enableSelectExplore(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())
}
return true
@ -235,47 +237,10 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
}.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() {
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 {
newText?.let {
initLiveDataBookSource(it)
@ -307,6 +272,21 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
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?) {
super.onActivityResult(requestCode, resultCode, data)
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 ->
try {
uri.readText(this)?.let {
@ -328,22 +308,24 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
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) {
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.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import com.jayway.jsonpath.JsonPath
import io.legado.app.App
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 {
val json = GSON.toJson(sources)
val file =
FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportBookSource.json")
file.writeText(json)
FileUtils.createFileIfNotExist(file, "exportBookSource.json")
.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 {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {

@ -3,7 +3,6 @@ package io.legado.app.ui.config
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
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.Restore
import io.legado.app.help.storage.WebDavHelp
import io.legado.app.lib.dialogs.alert
import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.utils.getPrefString
import io.legado.app.utils.isContentPath
import io.legado.app.utils.toast
import kotlinx.coroutines.Dispatchers.Main
import org.jetbrains.anko.toast
import java.io.File
object BackupRestoreUi {
private const val selectFolderRequestCode = 21
@ -79,31 +78,7 @@ object BackupRestoreUi {
}
fun selectBackupFolder(fragment: Fragment, requestCode: Int = selectFolderRequestCode) {
fragment.alert {
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()
FilePicker.selectFolder(fragment, requestCode)
}
fun restore(fragment: Fragment) {
@ -120,13 +95,13 @@ object BackupRestoreUi {
Restore.restore(fragment.requireContext(), backupPath)
fragment.toast(R.string.restore_success)
} else {
selectRestoreFolder(fragment)
selectBackupFolder(fragment, restoreSelectRequestCode)
}
} else {
restoreUsePermission(fragment, backupPath)
}
} else {
selectRestoreFolder(fragment)
selectBackupFolder(fragment, restoreSelectRequestCode)
}
}
}
@ -147,79 +122,8 @@ object BackupRestoreUi {
.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) {
fragment.alert {
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()
FilePicker.selectFolder(fragment, oldDataRequestCode)
}
fun onFilePicked(requestCode: Int, currentPath: String) {
@ -243,6 +147,9 @@ object BackupRestoreUi {
selectFolderRequestCode -> {
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.help.AppConfig
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.receiver.SharedReceiverActivity
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.utils.*
@ -124,38 +122,9 @@ class OtherConfigFragment : PreferenceFragmentCompat(),
}
private fun selectDownloadPath() {
alert {
titleResource = R.string.select_folder
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()
FilePicker.selectFolder(this, requestCodeDownloadPath) {
removePref(PreferKey.downloadPath)
}
}
override fun onFilePicked(requestCode: Int, currentPath: String) {

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

@ -16,17 +16,15 @@ import io.legado.app.constant.EventBus
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
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.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.utils.*
import kotlinx.android.synthetic.main.activity_download.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.anko.alert
import org.jetbrains.anko.toast
@ -126,44 +124,14 @@ class DownloadActivity : VMBaseActivity<DownloadViewModel>(R.layout.activity_dow
override fun export(position: Int) {
exportPosition = position
alert {
titleResource = R.string.select_folder
items(resources.getStringArray(R.array.select_folder).toList()) { _, index ->
when (index) {
0 -> {
val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey)
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()
}
}
FilePicker.selectFolder(this, exportRequestCode) {
val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey)
if (path.isNullOrEmpty()) {
toast("没有默认路径")
} else {
startExport(path)
}
}.show()
}
}
private fun startExport(path: String) {

@ -42,7 +42,7 @@ class FileChooserDialog : DialogFragment(),
isShowHomeDir: Boolean = false,
isShowUpDir: Boolean = true,
isShowHideDir: Boolean = false,
allowExtensions: Array<String?>? = null,
allowExtensions: Array<String>? = null,
menus: Array<String>? = null
) {
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
get() = mode == DIRECTORY
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.base.adapter.ItemViewHolder
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.utils.ConvertUtils
import io.legado.app.ui.filechooser.utils.FilePickerIcon
import io.legado.app.ui.filechooser.utils.FileUtils
import kotlinx.android.synthetic.main.item_path_filepicker.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
@ -122,7 +122,7 @@ class FileAdapter(context: Context, val callBack: CallBack) :
interface CallBack {
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.base.adapter.ItemViewHolder
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.FilePickerIcon
import kotlinx.android.synthetic.main.item_path_filepicker.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
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

@ -60,7 +60,7 @@ object FileUtils {
@JvmOverloads
fun listDirs(
startDirPath: String,
excludeDirs: Array<String?>? = null, @SortType sortType: Int = BY_NAME_ASC
excludeDirs: Array<String>? = null, @SortType sortType: Int = BY_NAME_ASC
): Array<File?> {
var excludeDirs1 = excludeDirs
val dirList = ArrayList<File>()
@ -76,7 +76,7 @@ object FileUtils {
})
?: return arrayOfNulls(0)
if (excludeDirs1 == null) {
excludeDirs1 = arrayOfNulls(0)
excludeDirs1 = arrayOf()
}
for (dir in dirs) {
val file = dir.absoluteFile
@ -115,7 +115,7 @@ object FileUtils {
@JvmOverloads
fun listDirsAndFiles(
startDirPath: String,
allowExtensions: Array<String?>? = null
allowExtensions: Array<String>? = null
): Array<File?>? {
val dirs: Array<File?>?
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)
return file.listFiles { _, 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?>? {
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
import android.annotation.SuppressLint
import android.graphics.Bitmap
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.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) {
var sourceUrl: String? = null
var loginUrl: String? = null
var checking = false
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.FragmentStatePagerAdapter
import androidx.viewpager.widget.ViewPager
import com.github.houbb.opencc4j.util.ZhConverterUtil
import com.google.android.material.bottomnavigation.BottomNavigationView
import io.legado.app.App
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.PreferKey
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.storage.Backup
import io.legado.app.lib.theme.ATH
import io.legado.app.service.BaseReadAloudService
@ -53,21 +51,12 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
upVersion()
//初始化简繁转换引擎
when (AppConfig.chineseConverterType) {
1 -> Coroutine.async { ZhConverterUtil.toSimple("初始化") }
2 -> Coroutine.async { ZhConverterUtil.toTraditional("初始化") }
}
//自动更新书籍
if (AppConfig.autoRefreshBook) {
view_pager_main.postDelayed({
viewModel.upChapterList()
}, 1000)
}
//清楚过期数据
view_pager_main.postDelayed({
viewModel.clearExpiredData()
}, 3000)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {

@ -13,7 +13,6 @@ import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.postEvent
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.delay
import java.util.concurrent.TimeUnit
class MainViewModel(application: Application) : BaseViewModel(application) {
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() {
execute {
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.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
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.PreferKey
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.accentColor
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.widget.text.AutoCompleteTextView
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.fragment_bookshelf.*
import kotlinx.android.synthetic.main.view_tab_layout.*
@ -61,7 +66,7 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
super.onCompatOptionsItemSelected(item)
when (item.itemId) {
R.id.menu_search -> startActivity<SearchActivity>()
R.id.menu_bookshelf_layout -> selectBookshelfLayout()
R.id.menu_bookshelf_layout -> configBookshelf()
R.id.menu_group_manage -> GroupManageDialog()
.show(childFragmentManager, "groupManageDialog")
R.id.menu_add_local -> startActivity<ImportBookActivity>()
@ -145,14 +150,35 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
}
}
private fun selectBookshelfLayout() {
selector(
title = "选择书架布局",
items = resources.getStringArray(R.array.bookshelf_layout).toList()
) { _, index ->
putPrefInt(PreferKey.bookshelfLayout, index)
activity?.recreate()
}
@SuppressLint("InflateParams")
private fun configBookshelf() {
requireContext().alert(titleResource = R.string.bookshelf_layout) {
val bookshelfLayout = getPrefInt(PreferKey.bookshelfLayout)
val bookshelfSort = getPrefInt(PreferKey.bookshelfSort)
val root = LayoutInflater.from(requireContext())
.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")

@ -103,10 +103,16 @@ class BooksFragment : BaseFragment(R.layout.fragment_books),
-3 -> App.db.bookDao().observeAudio()
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
.calculateDiff(BooksDiffCallBack(ArrayList(booksAdapter.getItems()), it))
booksAdapter.setItems(it, diffResult)
.calculateDiff(BooksDiffCallBack(ArrayList(booksAdapter.getItems()), books))
booksAdapter.setItems(books, diffResult)
})
}

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.SubMenu
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil
@ -19,13 +20,14 @@ import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.BookHelp
import io.legado.app.help.ItemTouchCallback
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.dialogs.*
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryTextColor
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.widget.SelectActionBar
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 org.jetbrains.anko.toast
import java.io.File
import java.io.FileNotFoundException
class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activity_replace_rule),
@ -47,7 +48,8 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
override val viewModel: ReplaceRuleViewModel
get() = getViewModel(ReplaceRuleViewModel::class.java)
private val importRecordKey = "replaceRuleRecordKey"
private val importSource = 132
private val importRequestCode = 132
private val exportRequestCode = 65
private lateinit var adapter: ReplaceRuleAdapter
private var groups = hashSetOf<String>()
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_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)
}
@ -186,7 +189,7 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
when (item?.itemId) {
R.id.menu_enable_selection -> viewModel.enableSelection(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
}
@ -233,43 +236,6 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
}.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 {
observeReplaceRuleData("%$newText%")
return false
@ -279,10 +245,25 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
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?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
importSource -> if (resultCode == Activity.RESULT_OK) {
importRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri ->
try {
uri.readText(this)?.let {
@ -292,25 +273,32 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
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) {
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() {
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.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import io.legado.app.App
import io.legado.app.R
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.storage.Backup
import io.legado.app.help.storage.ImportOldData
import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.*
import org.jetbrains.anko.toast
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 {
val json = GSON.toJson(rules)
val file =
FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportReplaceRule.json")
file.writeText(json)
val json = GSON.toJson(sources)
FileUtils.createFileIfNotExist(file, "exportReplaceRule.json")
.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 {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {

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

@ -21,8 +21,9 @@ class ReplaceEditViewModel(application: Application) : BaseViewModel(application
}
} else {
bundle.getString("pattern")?.let { pattern ->
val isRegex = bundle.getBoolean("isRegex")
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() {
webView.webViewClient = object : WebViewClient() {
web_view.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
@ -78,8 +78,19 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
}
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
domStorageEnabled = 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 html = viewModel.clHtml(content)
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 {
//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 {
upJavaScriptEnable()
webView.loadUrl(it.url, it.headerMap)
web_view.loadUrl(it.url, it.headerMap)
})
}
@SuppressLint("SetJavaScriptEnabled")
private fun upJavaScriptEnable() {
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 {
event?.let {
when (keyCode) {
KeyEvent.KEYCODE_BACK -> if (event.isTracking && !event.isCanceled && webView.canGoBack()) {
if (webView.copyBackForwardList().size > 1) {
webView.goBack()
KeyEvent.KEYCODE_BACK -> if (event.isTracking && !event.isCanceled && web_view.canGoBack()) {
if (web_view.copyBackForwardList().size > 1) {
web_view.goBack()
return true
}
}
@ -168,8 +191,8 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
viewModel.textToSpeech.stop()
upTtsMenu(false)
} else {
webView.settings.javaScriptEnabled = true
webView.evaluateJavascript("document.documentElement.outerHTML") {
web_view.settings.javaScriptEnabled = true
web_view.evaluateJavascript("document.documentElement.outerHTML") {
val html = StringEscapeUtils.unescapeJson(it)
val text = Jsoup.clean(html, Whitelist.none())
.replace(Regex("""&\w+;"""), "")

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.SubMenu
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil
@ -20,12 +21,11 @@ import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.RssSource
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.theme.ATH
import io.legado.app.lib.theme.primaryTextColor
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.rss.source.edit.RssSourceEditActivity
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.startActivityForResult
import org.jetbrains.anko.toast
import java.io.FileNotFoundException
import java.io.File
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)
private val importRecordKey = "rssSourceRecordKey"
private val qrRequestCode = 101
private val importSource = 124
private val importRequestCode = 124
private val exportRequestCode = 65
private lateinit var adapter: RssSourceAdapter
private var sourceLiveData: LiveData<List<RssSource>>? = null
private var groups = hashSetOf<String>()
@ -79,7 +80,8 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
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_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode)
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_disable_selection -> viewModel.disableSelection(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 -> {
}
}
@ -248,46 +250,25 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
}.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) {
if (requestCode == importSource) {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSourceFromFilePath(currentPath) { msg ->
title_bar.snackbar(msg)
when (requestCode) {
importRequestCode -> {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSourceFromFilePath(currentPath) { msg ->
title_bar.snackbar(msg)
}
}
exportRequestCode -> viewModel.exportSelection(
adapter.getSelection(),
File(currentPath)
)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
importSource -> if (resultCode == Activity.RESULT_OK) {
importRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri ->
try {
uri.readText(this)?.let {
@ -297,17 +278,6 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
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) {
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.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import com.jayway.jsonpath.JsonPath
import io.legado.app.App
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 {
val json = GSON.toJson(sources)
val file =
FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportRssSource.json")
file.writeText(json)
FileUtils.createFileIfNotExist(file, "exportRssSource.json")
.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 {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {

@ -1,49 +1,55 @@
package io.legado.app.ui.welcome
import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Intent
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.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.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainActivity
import io.legado.app.utils.getPrefBoolean
import kotlinx.android.synthetic.main.activity_welcome.*
import org.jetbrains.anko.startActivity
import java.util.concurrent.TimeUnit
open class WelcomeActivity : BaseActivity(R.layout.activity_welcome) {
override fun onActivityCreated(savedInstanceState: Bundle?) {
iv_bg.setColorFilter(accentColor)
iv_book.setColorFilter(accentColor)
vw_title_line.setBackgroundColor(accentColor)
// 避免从桌面启动程序后,会重新实例化入口类的activity
if (intent.flags and Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT != 0) {
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
})
welAnimator.start()
private fun startMainActivity() {
startActivity<MainActivity>()
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.appcompat.widget.PopupMenu
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 kotlinx.android.synthetic.main.view_select_action_bar.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
@ -18,7 +20,8 @@ class SelectActionBar(context: Context, attrs: AttributeSet?) : FrameLayout(cont
private var selMenu: PopupMenu? = null
init {
setBackgroundResource(R.color.background_menu)
setBackgroundColor(context.bottomBackground)
elevation = 10.dp.toFloat()
View.inflate(context, R.layout.view_select_action_bar, this)
cb_selected_all.setOnCheckedChangeListener { buttonView, isChecked ->
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.lib.dialogs.alert
import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.utils.*
import kotlinx.android.synthetic.main.dialog_font_select.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.anko.toast
import java.io.File
class FontSelectDialog : BaseDialogFragment(),
@ -107,41 +107,11 @@ class FontSelectDialog : BaseDialogFragment(),
private fun openFolder() {
launch(Main) {
alert {
titleResource = R.string.select_folder
items(resources.getStringArray(R.array.select_folder).toList()) { _, index ->
when (index) {
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()
FilePicker.selectFolder(this@FontSelectDialog, fontFolderRequestCode) {
val path = "${FileUtils.getSdCardPath()}${File.separator}Fonts"
putPrefString(PreferKey.fontFolder, path)
getFontFilesByPermission(path)
}
}
}

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

@ -7,8 +7,10 @@ import android.os.Build
import android.view.View
import android.view.View.*
import android.view.inputmethod.InputMethodManager
import android.widget.RadioGroup
import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.get
import io.legado.app.App
@ -70,3 +72,25 @@ fun View.screenshot(): Bitmap? {
fun SeekBar.progressAdd(int: 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:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="6dp"
android:elevation="10dp"
android:background="@color/background"
app:labelVisibilityMode="labeled"
app:menu="@menu/main_bnv"

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

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

@ -1,15 +1,71 @@
<?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:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:gravity="center_horizontal">
<ImageView
android:id="@+id/iv_bg"
<View
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_height="match_parent"
android:src="@drawable/image_welcome"
android:layout_height="wrap_content"
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: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:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
android:layout_gravity="center"
android:scrollbars="none" />
</FrameLayout>

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

@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_scan"
android:id="@+id/menu_scan"
android:icon="@drawable/ic_scan"
android:title="@string/scan_qr_code"
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>
</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">
<item>@string/indent_0</item>
<item>@string/indent_1</item>
@ -111,18 +103,6 @@
<item>@string/screen_sensor</item>
</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">
<attr name="batteryOrientation">
<enum name="horizontal" value="0" />

@ -15,7 +15,7 @@
<color name="background">@color/md_grey_50</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="transparent30">#30000000</color>

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

@ -28,6 +28,12 @@
android:key="volumeKeyPage"
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
android:defaultValue="false"
android:title="@string/click_all_next_page"

@ -60,6 +60,14 @@
app:cpv_dialogType="preset"
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
@ -90,6 +98,14 @@
app:cpv_dialogType="preset"
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>
</androidx.preference.PreferenceScreen>
Loading…
Cancel
Save