Merge pull request #32 from gedoor/master

up
pull/379/head
口口吕 5 years ago committed by GitHub
commit b92ab3a7af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/src/main/assets/help.md
  2. 13
      app/src/main/assets/updateLog.md
  3. 2
      app/src/main/java/io/legado/app/data/entities/Book.kt
  4. 3
      app/src/main/java/io/legado/app/help/LauncherIconHelp.kt
  5. 46
      app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt
  6. 57
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  7. 1
      app/src/main/java/io/legado/app/help/storage/OldBook.kt
  8. 50
      app/src/main/java/io/legado/app/model/Debug.kt
  9. 93
      app/src/main/java/io/legado/app/model/SearchBookModel.kt
  10. 7
      app/src/main/java/io/legado/app/model/WebBook.kt
  11. 32
      app/src/main/java/io/legado/app/model/webBook/BookList.kt
  12. 4
      app/src/main/java/io/legado/app/service/AudioPlayService.kt
  13. 6
      app/src/main/java/io/legado/app/service/DownloadService.kt
  14. 7
      app/src/main/java/io/legado/app/service/help/ReadBook.kt
  15. 2
      app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt
  16. 7
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt
  17. 21
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
  18. 8
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt
  19. 32
      app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
  20. 2
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  21. 16
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  22. 7
      app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt
  23. 2
      app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt
  24. 100
      app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt
  25. 2
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt
  26. 8
      app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
  27. 6
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt
  28. 2
      app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt
  29. 8
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt
  30. 17
      app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt
  31. 2
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
  32. 110
      app/src/main/java/io/legado/app/ui/widget/SearchView.kt
  33. 185
      app/src/main/java/io/legado/app/ui/widget/ShadowLayout.kt
  34. 9
      app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt
  35. 21
      app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt
  36. 6
      app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt
  37. 7
      app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt
  38. 85
      app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt
  39. 29
      app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt
  40. 22
      app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt
  41. 10
      app/src/main/res/drawable/bg_prefs_color.xml
  42. 15
      app/src/main/res/drawable/ic_cfg_about.xml
  43. 9
      app/src/main/res/drawable/ic_cfg_backup.xml
  44. 5
      app/src/main/res/drawable/ic_cfg_e_lnk.xml
  45. 12
      app/src/main/res/drawable/ic_cfg_jz.xml
  46. 12
      app/src/main/res/drawable/ic_cfg_other.xml
  47. 12
      app/src/main/res/drawable/ic_cfg_replace.xml
  48. 12
      app/src/main/res/drawable/ic_cfg_source.xml
  49. 9
      app/src/main/res/drawable/ic_cfg_theme.xml
  50. 9
      app/src/main/res/drawable/ic_cfg_web.xml
  51. 27
      app/src/main/res/drawable/ic_help.xml
  52. 52
      app/src/main/res/drawable/ic_launcher1_b.xml
  53. 8
      app/src/main/res/drawable/ic_search_hint.xml
  54. 2
      app/src/main/res/layout/activity_main.xml
  55. 2
      app/src/main/res/layout/activity_rss_read.xml
  56. 90
      app/src/main/res/layout/item_bookshelf_grid.xml
  57. 65
      app/src/main/res/layout/view_preference.xml
  58. 29
      app/src/main/res/layout/view_preference_category.xml
  59. 10
      app/src/main/res/layout/view_search.xml
  60. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  61. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  62. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  63. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  64. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  65. 1
      app/src/main/res/values-night/colors.xml
  66. 13
      app/src/main/res/values/attrs.xml
  67. 1
      app/src/main/res/values/colors.xml
  68. 5
      app/src/main/res/values/strings.xml
  69. 30
      app/src/main/res/xml/about.xml
  70. 25
      app/src/main/res/xml/donate.xml
  71. 24
      app/src/main/res/xml/pref_config_backup.xml
  72. 16
      app/src/main/res/xml/pref_config_other.xml
  73. 14
      app/src/main/res/xml/pref_config_theme.xml
  74. 44
      app/src/main/res/xml/pref_main.xml

@ -1,3 +1,5 @@
## 常见问题
1.为什么第一次安装好之后什么东西都没有?
* 因为阅读只是一个转码工具,不提供内容,第一次安装app,需要自己手动导入书源,可以从QQ群、公众号“开源阅读软件”、酷安评论里获取由书友制作分享的书源。

@ -1,9 +1,20 @@
## 更新日志
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
* 请关注[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。
* 弄了个企业公众号[开源阅读](),后面弄好后会把原来的[开源阅读软件]()迁移过来
**2020/03/19**
* 美化界面我的 by yangyxd
* 优化搜索
**2020/03/18**
* 尝试修复搜索时崩溃
* 解决看过书籍的移到顶部需要向上滚动才能看到的bug
* 只有再书源被删除找不到书源时才会自动换源
* 美化界面 by yangyxd
* 订阅后台播放
**2020/03/16**
* 弄了个企业公众号[开源阅读](),后面弄好后会把原来的开源阅读软件迁移过来
* 修复滚动模式切换章节位置不归0的bug
* 修复文字选择更多菜单在部分手机上报错的bug
* 修复文字选择菜单问题

@ -32,7 +32,7 @@ data class Book(
var customIntro: String? = null, // 简介内容(用户修改)
var charset: String? = null, // 自定义字符集名称(仅适用于本地书籍)
var type: Int = 0, // 0:text 1:audio
var group: Int = 1, // 自定义分组索引号
var group: Int = 0, // 自定义分组索引号
var latestChapterTitle: String? = null, // 最新章节标题
var latestChapterTime: Long = System.currentTimeMillis(), // 最新章节标题更新时间
var lastCheckTime: Long = System.currentTimeMillis(), // 最近一次更新书籍信息的时间

@ -19,7 +19,8 @@ object LauncherIconHelp {
ComponentName(App.INSTANCE, Launcher2::class.java.name),
ComponentName(App.INSTANCE, Launcher3::class.java.name),
ComponentName(App.INSTANCE, Launcher4::class.java.name),
ComponentName(App.INSTANCE, Launcher5::class.java.name)
ComponentName(App.INSTANCE, Launcher5::class.java.name),
ComponentName(App.INSTANCE, Launcher6::class.java.name)
)
fun changeIcon(icon: String?) {

@ -6,7 +6,7 @@ import kotlin.coroutines.CoroutineContext
class Coroutine<T>(
scope: CoroutineScope,
val scope: CoroutineScope,
context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> T
) {
@ -28,9 +28,10 @@ class Coroutine<T>(
private val job: Job
private var start: VoidCallback? = null
private var success: Callback<T?>? = null
private var success: Callback<T>? = null
private var error: Callback<Throwable>? = null
private var finally: VoidCallback? = null
private var cancel: VoidCallback? = null
private var timeMillis: Long? = null
private var errorReturn: Result<T>? = null
@ -45,7 +46,7 @@ class Coroutine<T>(
get() = job.isCompleted
init {
this.job = executeInternal(scope, context, block)
this.job = executeInternal(context, block)
}
fun timeout(timeMillis: () -> Long): Coroutine<T> {
@ -78,7 +79,7 @@ class Coroutine<T>(
fun onSuccess(
context: CoroutineContext? = null,
block: suspend CoroutineScope.(T?) -> Unit
block: suspend CoroutineScope.(T) -> Unit
): Coroutine<T> {
this.success = Callback(context, block)
return this@Coroutine
@ -100,9 +101,28 @@ class Coroutine<T>(
return this@Coroutine
}
fun onCancel(
context: CoroutineContext? = null,
block: suspend CoroutineScope.() -> Unit
): Coroutine<T> {
this.cancel = VoidCallback(context, block)
return this@Coroutine
}
//取消当前任务
fun cancel(cause: CancellationException? = null) {
job.cancel(cause)
cancel?.let {
MainScope().launch {
if (null == it.context) {
it.block.invoke(scope)
} else {
withContext(scope.coroutineContext.plus(it.context)) {
it.block.invoke(this)
}
}
}
}
}
fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle {
@ -110,7 +130,6 @@ class Coroutine<T>(
}
private fun executeInternal(
scope: CoroutineScope,
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): Job {
@ -118,21 +137,27 @@ class Coroutine<T>(
try {
start?.let { dispatchVoidCallback(this, it) }
val value = executeBlock(scope, context, timeMillis ?: 0L, block)
success?.let { dispatchCallback(this, value, it) }
if (isActive) {
success?.let { dispatchCallback(this, value, it) }
}
} catch (e: Throwable) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}
val consume: Boolean = errorReturn?.value?.let { value ->
success?.let { dispatchCallback(this, value, it) }
if (isActive) {
success?.let { dispatchCallback(this, value, it) }
}
true
} ?: false
if (!consume) {
if (!consume && isActive) {
error?.let { dispatchCallback(this, e, it) }
}
} finally {
finally?.let { dispatchVoidCallback(this, it) }
if (isActive) {
finally?.let { dispatchVoidCallback(this, it) }
}
}
}
}
@ -152,6 +177,7 @@ class Coroutine<T>(
value: R,
callback: Callback<R>
) {
if (!scope.isActive) return
if (null == callback.context) {
callback.block.invoke(scope, value)
} else {
@ -166,7 +192,7 @@ class Coroutine<T>(
context: CoroutineContext,
timeMillis: Long,
noinline block: suspend CoroutineScope.() -> T
): T? {
): T {
return withContext(scope.coroutineContext.plus(context)) {
if (timeMillis > 0L) withTimeout(timeMillis) {
block()

@ -54,35 +54,37 @@ object Backup {
suspend fun backup(context: Context, path: String = legadoPath) {
context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis())
withContext(IO) {
writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath)
writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath)
writeListToJson(App.db.bookSourceDao().all, "bookSource.json", backupPath)
writeListToJson(App.db.rssSourceDao().all, "rssSource.json", backupPath)
writeListToJson(App.db.rssStarDao().all, "rssStar.json", backupPath)
writeListToJson(App.db.replaceRuleDao().all, "replaceRule.json", backupPath)
GSON.toJson(ReadBookConfig.configList)?.let {
FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.readConfigFileName)
.writeText(it)
}
Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp ->
val edit = sp.edit()
App.INSTANCE.defaultSharedPreferences.all.map {
when (val value = it.value) {
is Int -> edit.putInt(it.key, value)
is Boolean -> edit.putBoolean(it.key, value)
is Long -> edit.putLong(it.key, value)
is Float -> edit.putFloat(it.key, value)
is String -> edit.putString(it.key, value)
else -> Unit
synchronized(this@Backup) {
writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath)
writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath)
writeListToJson(App.db.bookSourceDao().all, "bookSource.json", backupPath)
writeListToJson(App.db.rssSourceDao().all, "rssSource.json", backupPath)
writeListToJson(App.db.rssStarDao().all, "rssStar.json", backupPath)
writeListToJson(App.db.replaceRuleDao().all, "replaceRule.json", backupPath)
GSON.toJson(ReadBookConfig.configList)?.let {
FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.readConfigFileName)
.writeText(it)
}
Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp ->
val edit = sp.edit()
App.INSTANCE.defaultSharedPreferences.all.map {
when (val value = it.value) {
is Int -> edit.putInt(it.key, value)
is Boolean -> edit.putBoolean(it.key, value)
is Long -> edit.putLong(it.key, value)
is Float -> edit.putFloat(it.key, value)
is String -> edit.putString(it.key, value)
else -> Unit
}
}
edit.commit()
}
WebDavHelp.backUpWebDav(backupPath)
if (path.isContentPath()) {
copyBackup(context, Uri.parse(path))
} else {
copyBackup(File(path))
}
edit.commit()
}
WebDavHelp.backUpWebDav(backupPath)
if (path.isContentPath()) {
copyBackup(context, Uri.parse(path))
} else {
copyBackup(File(path))
}
}
}
@ -96,6 +98,7 @@ object Backup {
@Throws(java.lang.Exception::class)
private fun copyBackup(context: Context, uri: Uri) {
DocumentFile.fromTreeUri(context, uri)?.let { treeDoc ->
for (fileName in backupFileNames) {
val file = File(backupPath + File.separator + fileName)

@ -40,7 +40,6 @@ object OldBook {
book.durChapterTitle = jsonItem.readString("$.durChapterName")
book.durChapterPos = jsonItem.readInt("$.durChapterPage") ?: 0
book.durChapterTime = jsonItem.readLong("$.finalDate") ?: 0
book.group = jsonItem.readInt("$.group") ?: 0
book.intro = jsonItem.readString("$.bookInfoBean.introduce")
book.latestChapterTitle = jsonItem.readString("$.lastChapterName")
book.lastCheckCount = jsonItem.readInt("$.newChapters") ?: 0

@ -56,9 +56,7 @@ object Debug {
log(debugSource, "︾开始解析")
Rss.getArticles(rssSource, null)
.onSuccess {
if (it == null) {
log(debugSource, "︽解析失败", state = -1)
} else if(it.articles.isEmpty()) {
if (it.articles.isEmpty()) {
log(debugSource, "⇒列表页解析成功,为空")
log(debugSource, "︽解析完成", state = 1000)
} else {
@ -140,14 +138,12 @@ object Debug {
log(debugSource, "︾开始解析发现页")
val explore = webBook.exploreBook(url, 1)
.onSuccess { exploreBooks ->
exploreBooks?.let {
if (exploreBooks.isNotEmpty()) {
log(debugSource, "︽发现页解析完成")
log(debugSource, showTime = false)
infoDebug(webBook, exploreBooks[0].toBook())
} else {
log(debugSource, "︽未获取到书籍", state = -1)
}
if (exploreBooks.isNotEmpty()) {
log(debugSource, "︽发现页解析完成")
log(debugSource, showTime = false)
infoDebug(webBook, exploreBooks[0].toBook())
} else {
log(debugSource, "︽未获取到书籍", state = -1)
}
}
.onError {
@ -164,14 +160,12 @@ object Debug {
log(debugSource, "︾开始解析搜索页")
val search = webBook.searchBook(key, 1)
.onSuccess { searchBooks ->
searchBooks?.let {
if (searchBooks.isNotEmpty()) {
log(debugSource, "︽搜索页解析完成")
log(debugSource, showTime = false)
infoDebug(webBook, searchBooks[0].toBook())
} else {
log(debugSource, "︽未获取到书籍", state = -1)
}
if (searchBooks.isNotEmpty()) {
log(debugSource, "︽搜索页解析完成")
log(debugSource, showTime = false)
infoDebug(webBook, searchBooks[0].toBook())
} else {
log(debugSource, "︽未获取到书籍", state = -1)
}
}
.onError {
@ -197,16 +191,14 @@ object Debug {
private fun tocDebug(webBook: WebBook, book: Book) {
log(debugSource, "︾开始解析目录页")
val chapterList = webBook.getChapterList(book)
.onSuccess { chapterList ->
chapterList?.let {
if (it.isNotEmpty()) {
log(debugSource, "︽目录页解析完成")
log(debugSource, showTime = false)
val nextChapterUrl = if (it.size > 1) it[1].url else null
contentDebug(webBook, book, it[0], nextChapterUrl)
} else {
log(debugSource, "︽目录列表为空", state = -1)
}
.onSuccess {
if (it.isNotEmpty()) {
log(debugSource, "︽目录页解析完成")
log(debugSource, showTime = false)
val nextChapterUrl = if (it.size > 1) it[1].url else null
contentDebug(webBook, book, it[0], nextChapterUrl)
} else {
log(debugSource, "︽目录列表为空", state = -1)
}
}
.onError {

@ -0,0 +1,93 @@
package io.legado.app.model
import io.legado.app.App
import io.legado.app.data.entities.SearchBook
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.utils.getPrefString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors
class SearchBookModel(private val scope: CoroutineScope, private val callBack: CallBack) {
private var searchPool =
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher()
private var mSearchId = System.currentTimeMillis()
private var searchPage = 1
private var searchKey: String = ""
private var task: Coroutine<*>? = null
private fun initSearchPool() {
searchPool =
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher()
}
fun search(searchId: Long, key: String) {
if (searchId != mSearchId) {
task?.cancel()
searchPool.close()
initSearchPool()
mSearchId = searchId
searchPage = 1
if (key.isEmpty()) {
return
} else {
this.searchKey = key
}
} else {
searchPage++
}
task = Coroutine.async(scope, searchPool) {
val searchGroup = App.INSTANCE.getPrefString("searchGroup") ?: ""
val bookSourceList = if (searchGroup.isBlank()) {
App.db.bookSourceDao().allEnabled
} else {
App.db.bookSourceDao().getEnabledByGroup(searchGroup)
}
for (item in bookSourceList) {
//task取消时自动取消 by (scope = this@execute)
WebBook(item).searchBook(
searchKey,
searchPage,
scope = this,
context = searchPool
)
.timeout(30000L)
.onSuccess(IO) {
if (searchId == mSearchId) {
callBack.onSearchSuccess(it)
}
}
}
}.onStart {
callBack.onSearchStart()
}
task?.invokeOnCompletion {
if (searchId == mSearchId) {
callBack.onSearchFinish()
}
}
}
fun cancelSearch() {
task?.cancel()
mSearchId = 0
callBack.onSearchCancel()
}
fun close() {
task?.cancel()
mSearchId = 0
searchPool.close()
}
interface CallBack {
fun onSearchStart()
fun onSearchSuccess(searchBooks: ArrayList<SearchBook>)
fun onSearchFinish()
fun onSearchCancel()
}
}

@ -27,13 +27,14 @@ class WebBook(val bookSource: BookSource) {
page: Int? = 1,
scope: CoroutineScope = Coroutine.DEFAULT,
context: CoroutineContext = Dispatchers.IO
): Coroutine<List<SearchBook>> {
): Coroutine<ArrayList<SearchBook>> {
return Coroutine.async(scope, context) {
searchBookSuspend(key, page)
searchBookSuspend(scope, key, page)
}
}
suspend fun searchBookSuspend(
scope: CoroutineScope,
key: String,
page: Int? = 1
): ArrayList<SearchBook> {
@ -47,6 +48,7 @@ class WebBook(val bookSource: BookSource) {
)
val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl)
return BookList.analyzeBookList(
scope,
res.body,
bookSource,
analyzeUrl,
@ -75,6 +77,7 @@ class WebBook(val bookSource: BookSource) {
)
val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl)
BookList.analyzeBookList(
scope,
res.body,
bookSource,
analyzeUrl,

@ -9,11 +9,15 @@ import io.legado.app.model.Debug
import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.NetworkUtils
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.isActive
object BookList {
@Throws(Exception::class)
fun analyzeBookList(
scope: CoroutineScope,
body: String?,
bookSource: BookSource,
analyzeUrl: AnalyzeUrl,
@ -28,12 +32,13 @@ object BookList {
)
)
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}")
if (!scope.isActive) throw CancellationException()
val analyzeRule = AnalyzeRule(null)
analyzeRule.setContent(body, baseUrl)
bookSource.bookUrlPattern?.let {
if (baseUrl.matches(it.toRegex())) {
Debug.log(bookSource.bookSourceUrl, "≡链接为详情页")
getInfoItem(analyzeRule, bookSource, baseUrl)?.let { searchBook ->
getInfoItem(scope, analyzeRule, bookSource, baseUrl)?.let { searchBook ->
searchBook.infoHtml = body
bookList.add(searchBook)
}
@ -59,7 +64,7 @@ object BookList {
collections = analyzeRule.getElements(ruleList)
if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) {
Debug.log(bookSource.bookSourceUrl, "└列表为空,按详情页解析")
getInfoItem(analyzeRule, bookSource, baseUrl)?.let { searchBook ->
getInfoItem(scope, analyzeRule, bookSource, baseUrl)?.let { searchBook ->
searchBook.infoHtml = body
bookList.add(searchBook)
}
@ -74,8 +79,9 @@ object BookList {
val ruleWordCount = analyzeRule.splitSourceRule(bookListRule.wordCount)
Debug.log(bookSource.bookSourceUrl, "└列表大小:${collections.size}")
for ((index, item) in collections.withIndex()) {
if (!scope.isActive) throw CancellationException()
getSearchItem(
item, analyzeRule, bookSource, baseUrl, index == 0,
scope, item, analyzeRule, bookSource, baseUrl, index == 0,
ruleName = ruleName, ruleBookUrl = ruleBookUrl, ruleAuthor = ruleAuthor,
ruleCoverUrl = ruleCoverUrl, ruleIntro = ruleIntro, ruleKind = ruleKind,
ruleLastChapter = ruleLastChapter, ruleWordCount = ruleWordCount
@ -93,7 +99,9 @@ object BookList {
return bookList
}
@Throws(Exception::class)
private fun getInfoItem(
scope: CoroutineScope,
analyzeRule: AnalyzeRule,
bookSource: BookSource,
baseUrl: String
@ -108,29 +116,37 @@ object BookList {
with(bookSource.getBookInfoRule()) {
init?.let {
if (it.isNotEmpty()) {
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则")
analyzeRule.setContent(analyzeRule.getElement(it))
}
}
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取书名")
searchBook.name = analyzeRule.getString(name)
Debug.log(bookSource.bookSourceUrl, "${searchBook.name}")
if (searchBook.name.isNotEmpty()) {
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取作者")
searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(author))
Debug.log(bookSource.bookSourceUrl, "${searchBook.author}")
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取分类")
searchBook.kind = analyzeRule.getStringList(kind)?.joinToString(",")
Debug.log(bookSource.bookSourceUrl, "${searchBook.kind}")
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取字数")
searchBook.wordCount = analyzeRule.getString(wordCount)
Debug.log(bookSource.bookSourceUrl, "${searchBook.wordCount}")
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节")
searchBook.latestChapterTitle = analyzeRule.getString(lastChapter)
Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}")
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取简介")
searchBook.intro = analyzeRule.getString(intro)
Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", true)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接")
searchBook.coverUrl = analyzeRule.getString(coverUrl, true)
Debug.log(bookSource.bookSourceUrl, "${searchBook.coverUrl}")
@ -140,7 +156,9 @@ object BookList {
return null
}
@Throws(Exception::class)
private fun getSearchItem(
scope: CoroutineScope,
item: Any,
analyzeRule: AnalyzeRule,
bookSource: BookSource,
@ -162,30 +180,38 @@ object BookList {
searchBook.originOrder = bookSource.customOrder
analyzeRule.book = searchBook
analyzeRule.setContent(item)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取书名", log)
searchBook.name = analyzeRule.getString(ruleName)
Debug.log(bookSource.bookSourceUrl, "${searchBook.name}", log)
if (searchBook.name.isNotEmpty()) {
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取作者", log)
searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(ruleAuthor))
Debug.log(bookSource.bookSourceUrl, "${searchBook.author}", log)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取分类", log)
searchBook.kind = analyzeRule.getStringList(ruleKind)?.joinToString(",")
Debug.log(bookSource.bookSourceUrl, "${searchBook.kind}", log)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取字数", log)
searchBook.wordCount = analyzeRule.getString(ruleWordCount)
Debug.log(bookSource.bookSourceUrl, "${searchBook.wordCount}", log)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节", log)
searchBook.latestChapterTitle = analyzeRule.getString(ruleLastChapter)
Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}", log)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取简介", log)
searchBook.intro = analyzeRule.getString(ruleIntro)
Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", log, true)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log)
analyzeRule.getString(ruleCoverUrl).let {
if (it.isNotEmpty()) searchBook.coverUrl = NetworkUtils.getAbsoluteURL(baseUrl, it)
}
Debug.log(bookSource.bookSourceUrl, "${searchBook.coverUrl}", log)
if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取详情页链接", log)
searchBook.bookUrl = analyzeRule.getString(ruleBookUrl, true)
if (searchBook.bookUrl.isEmpty()) {

@ -18,9 +18,9 @@ import androidx.core.app.NotificationCompat
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseService
import io.legado.app.constant.IntentAction
import io.legado.app.constant.AppConst
import io.legado.app.constant.EventBus
import io.legado.app.constant.IntentAction
import io.legado.app.constant.Status
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp
@ -275,7 +275,7 @@ class AudioPlayService : BaseService(),
AudioPlay.book?.let { book ->
AudioPlay.webBook?.getContent(book, chapter, scope = this)
?.onSuccess(IO) { content ->
if (content.isNullOrEmpty()) {
if (content.isEmpty()) {
withContext(Main) {
toast("未获取到资源链接")
}

@ -130,10 +130,8 @@ class DownloadService : BaseService() {
// notificationContent = "启动:" + chapter.title
//}
.onSuccess(IO) { content ->
content?.let {
downloadCount[entry.key]?.increaseSuccess()
BookHelp.saveContent(book, chapter, content)
}
downloadCount[entry.key]?.increaseSuccess()
BookHelp.saveContent(book, chapter, content)
}
.onFinally(IO) {
synchronized(this@DownloadService) {

@ -64,6 +64,11 @@ object ReadBook {
}
}
fun upMsg(msg: String?) {
this.msg = msg
callBack?.upContent()
}
fun moveToNextPage() {
durPageIndex++
callBack?.upContent()
@ -240,7 +245,7 @@ object ReadBook {
book?.let { book ->
webBook?.getContent(book, chapter)
?.onSuccess(Dispatchers.IO) { content ->
if (content.isNullOrEmpty()) {
if (content.isEmpty()) {
contentLoadFinish(
chapter,
App.INSTANCE.getString(R.string.content_empty),

@ -72,7 +72,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
execute {
AudioPlay.webBook?.getChapterList(book, this)
?.onSuccess(Dispatchers.IO) { cList ->
if (!cList.isNullOrEmpty()) {
if (cList.isNotEmpty()) {
if (changeDruChapterIndex == null) {
App.db.bookChapterDao().insert(*cList.toTypedArray())
AudioPlay.chapterSize = cList.size

@ -51,14 +51,13 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
fun search() {
task = execute {
searchStateData.postValue(true)
val bookSourceList = App.db.bookSourceDao().allEnabled
for (item in bookSourceList) {
//task取消时自动取消 by (scope = this@execute)
WebBook(item).searchBook(name, scope = this@execute, context = searchPool)
.timeout(30000L)
.onSuccess(Dispatchers.IO) {
if (it != null && it.isNotEmpty()) {
if (it.isNotEmpty()) {
val searchBook = it[0]
if (searchBook.name == name && searchBook.author == author
&& !searchBook.coverUrl.isNullOrEmpty()
@ -72,6 +71,10 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
}
}
}
}.onStart {
searchStateData.postValue(true)
}.onCancel {
searchStateData.postValue(false)
}
task?.invokeOnCompletion {

@ -72,14 +72,13 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
fun search() {
task = execute {
searchStateData.postValue(true)
val bookSourceList = App.db.bookSourceDao().allEnabled
for (item in bookSourceList) {
//task取消时自动取消 by (scope = this@execute)
WebBook(item).searchBook(name, scope = this@execute, context = searchPool)
.timeout(30000L)
.onSuccess(IO) {
it?.forEach { searchBook ->
it.forEach { searchBook ->
if (searchBook.name == name && searchBook.author == author) {
if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) {
if (searchBook.tocUrl.isEmpty()) {
@ -95,6 +94,10 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
}
}
}
}.onStart {
searchStateData.postValue(true)
}.onCancel {
searchStateData.postValue(false)
}
task?.invokeOnCompletion {
@ -107,7 +110,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource ->
WebBook(bookSource).getBookInfo(book, this)
.onSuccess {
it?.let { loadChapter(it) }
loadChapter(it)
}.onError {
debug { context.getString(R.string.error_get_book_info) }
}
@ -119,13 +122,11 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
execute {
App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource ->
WebBook(bookSource).getChapterList(book, this)
.onSuccess(IO) {
it?.let { chapters ->
if (chapters.isNotEmpty()) {
book.latestChapterTitle = chapters.last().title
val searchBook: SearchBook = book.toSearchBook()
searchFinish(searchBook)
}
.onSuccess(IO) { chapters ->
if (chapters.isNotEmpty()) {
book.latestChapterTitle = chapters.last().title
val searchBook: SearchBook = book.toSearchBook()
searchFinish(searchBook)
}
}.onError {
debug { context.getString(R.string.error_get_chapter_list) }

@ -35,11 +35,9 @@ class ExploreShowViewModel(application: Application) : BaseViewModel(application
WebBook(source).exploreBook(url, page, this)
.timeout(30000L)
.onSuccess(IO) { searchBooks ->
searchBooks?.let {
booksData.postValue(searchBooks)
App.db.searchBookDao().insert(*searchBooks.toTypedArray())
page++
}
booksData.postValue(searchBooks)
App.db.searchBookDao().insert(*searchBooks.toTypedArray())
page++
}
}
}

@ -59,13 +59,11 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource ->
WebBook(bookSource).getBookInfo(book, this)
.onSuccess(IO) {
it?.let {
bookData.postValue(book)
if (inBookshelf) {
App.db.bookDao().update(book)
}
loadChapter(it, changeDruChapterIndex)
bookData.postValue(book)
if (inBookshelf) {
App.db.bookDao().update(book)
}
loadChapter(it, changeDruChapterIndex)
}.onError {
toast(R.string.error_get_book_info)
}
@ -92,20 +90,18 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource ->
WebBook(bookSource).getChapterList(book, this)
.onSuccess(IO) {
it?.let {
if (it.isNotEmpty()) {
if (inBookshelf) {
App.db.bookDao().update(book)
App.db.bookChapterDao().insert(*it.toTypedArray())
}
if (changeDruChapterIndex == null) {
chapterListData.postValue(it)
} else {
changeDruChapterIndex(it)
}
if (it.isNotEmpty()) {
if (inBookshelf) {
App.db.bookDao().update(book)
App.db.bookChapterDao().insert(*it.toTypedArray())
}
if (changeDruChapterIndex == null) {
chapterListData.postValue(it)
} else {
toast(R.string.chapter_list_empty)
changeDruChapterIndex(it)
}
} else {
toast(R.string.chapter_list_empty)
}
}.onError {
chapterListData.postValue(null)

@ -92,6 +92,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
override val headerHeight: Int get() = page_view.curPage.headerHeight
override fun onCreate(savedInstanceState: Bundle?) {
ReadBook.msg = null
Help.setOrientation(this)
super.onCreate(savedInstanceState)
}
@ -682,6 +683,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
mHandler.removeCallbacks(keepScreenRunnable)
textActionMenu?.dismiss()
page_view.onDestroy()
ReadBook.msg = null
if (!BuildConfig.DEBUG) {
SyncBookProgress.uploadBookProgress()
Backup.autoBack(this)

@ -112,20 +112,21 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
} else {
ReadBook.webBook?.getChapterList(book, this)
?.onSuccess(IO) { cList ->
if (!cList.isNullOrEmpty()) {
if (cList.isNotEmpty()) {
if (changeDruChapterIndex == null) {
App.db.bookChapterDao().insert(*cList.toTypedArray())
App.db.bookDao().update(book)
ReadBook.chapterSize = cList.size
ReadBook.upMsg(null)
ReadBook.loadContent(resetPageOffset = true)
} else {
changeDruChapterIndex(cList)
}
} else {
toast(R.string.error_load_toc)
ReadBook.upMsg(context.getString(R.string.error_load_toc))
}
}?.onError {
toast(R.string.error_load_toc)
ReadBook.upMsg(context.getString(R.string.error_load_toc))
}
}
}
@ -133,6 +134,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
fun changeTo(book1: Book) {
execute {
ReadBook.upMsg(null)
ReadBook.book?.let {
App.db.bookDao().delete(it)
}
@ -159,7 +161,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
execute {
App.db.bookSourceDao().allTextEnabled.forEach { source ->
try {
val searchBooks = WebBook(source).searchBookSuspend(name)
val searchBooks = WebBook(source).searchBookSuspend(this, name)
searchBooks.getOrNull(0)?.let {
if (it.name == name && (it.author == author || author == "")) {
changeTo(it.toBook())
@ -171,11 +173,9 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
}
}
}.onStart {
ReadBook.msg = "正在自动换源"
ReadBook.callBack?.upContent()
ReadBook.upMsg("正在自动换源")
}.onFinally {
ReadBook.msg = null
ReadBook.callBack?.upContent()
ReadBook.upMsg(null)
}
}

@ -49,9 +49,9 @@ class DiffCallBack(private val oldItems: List<SearchBook>, private val newItems:
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
val payload = Bundle()
val newItem = newItems[newItemPosition]
val oldItem = oldItems[oldItemPosition]
if (oldItem.name != newItem.name) {
payload.putString("name", newItem.name)
}
@ -73,6 +73,9 @@ class DiffCallBack(private val oldItems: List<SearchBook>, private val newItems:
if (oldItem.intro != newItem.intro) {
payload.putString("intro", newItem.intro)
}
if (payload.isEmpty) {
return null
}
return payload
}
}

@ -112,6 +112,7 @@ class SearchActivity : VMBaseActivity<SearchViewModel>(R.layout.activity_book_se
viewModel.saveSearchKey(query)
viewModel.search(it)
}
openOrCloseHistory(false)
return true
}
@ -128,6 +129,7 @@ class SearchActivity : VMBaseActivity<SearchViewModel>(R.layout.activity_book_se
openOrCloseHistory(hasFocus)
}
}
openOrCloseHistory(true)
}
private fun initRecyclerView() {

@ -7,82 +7,62 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.SearchBook
import io.legado.app.data.entities.SearchKeyword
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.WebBook
import io.legado.app.model.SearchBookModel
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefString
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.isActive
class SearchViewModel(application: Application) : BaseViewModel(application) {
private var searchPool =
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher()
private var task: Coroutine<*>? = null
class SearchViewModel(application: Application) : BaseViewModel(application)
, SearchBookModel.CallBack {
private val searchBookModel = SearchBookModel(this, this)
var isSearchLiveData = MutableLiveData<Boolean>()
var searchBookLiveData = MutableLiveData<List<SearchBook>>()
var searchKey: String = ""
var searchPage = 1
var isLoading = false
var searchBooks = arrayListOf<SearchBook>()
private var searchID = 0L
/**
* 开始搜索
*/
fun search(key: String) {
task?.cancel()
if (key.isEmpty() && searchKey.isEmpty()) {
return
} else if (key.isEmpty()) {
isLoading = true
searchPage++
} else if (key.isNotEmpty()) {
isLoading = true
searchPage = 1
searchKey = key
if (searchKey != key && key.isNotEmpty()) {
searchBookModel.cancelSearch()
searchBooks.clear()
searchBookLiveData.postValue(searchBooks)
searchID = System.currentTimeMillis()
searchKey = key
}
searchBookModel.search(searchID, searchKey)
}
override fun onSearchStart() {
isSearchLiveData.postValue(true)
task = execute {
val searchGroup = context.getPrefString("searchGroup") ?: ""
val bookSourceList = if (searchGroup.isBlank()) {
App.db.bookSourceDao().allEnabled
} else {
App.db.bookSourceDao().getEnabledByGroup(searchGroup)
}
for (item in bookSourceList) {
//task取消时自动取消 by (scope = this@execute)
WebBook(item).searchBook(
searchKey,
searchPage,
scope = this@execute,
context = searchPool
)
.timeout(30000L)
.onSuccess(IO) {
it?.let { list ->
if (context.getPrefBoolean(PreferKey.precisionSearch)) {
precisionSearch(list)
} else {
App.db.searchBookDao().insert(*list.toTypedArray())
mergeItems(list)
}
}
}
}
}
}
task?.invokeOnCompletion {
isSearchLiveData.postValue(false)
isLoading = false
override fun onSearchSuccess(searchBooks: ArrayList<SearchBook>) {
if (context.getPrefBoolean(PreferKey.precisionSearch)) {
precisionSearch(this, searchBooks)
} else {
App.db.searchBookDao().insert(*searchBooks.toTypedArray())
mergeItems(this, searchBooks)
}
}
override fun onSearchFinish() {
isSearchLiveData.postValue(false)
isLoading = false
}
override fun onSearchCancel() {
isSearchLiveData.postValue(false)
isLoading = false
}
/**
* 精确搜索处理
*/
private fun precisionSearch(searchBooks: List<SearchBook>) {
private fun precisionSearch(scope: CoroutineScope, searchBooks: List<SearchBook>) {
val books = arrayListOf<SearchBook>()
searchBooks.forEach { searchBook ->
if (searchBook.name.contains(searchKey, true)
@ -90,14 +70,16 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
) books.add(searchBook)
}
App.db.searchBookDao().insert(*books.toTypedArray())
mergeItems(books)
if (scope.isActive) {
mergeItems(scope, books)
}
}
/**
* 合并搜索结果并排序
*/
@Synchronized
private fun mergeItems(newDataS: List<SearchBook>) {
private fun mergeItems(scope: CoroutineScope, newDataS: List<SearchBook>) {
if (newDataS.isNotEmpty()) {
val copyDataS = ArrayList(searchBooks)
val searchBooksAdd = ArrayList<SearchBook>()
@ -141,6 +123,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
}
}
}
if (!scope.isActive) return
searchBooks.sortWith(Comparator { o1, o2 ->
if (o1.name == searchKey && o2.name != searchKey) {
1
@ -166,6 +149,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
0
}
})
if (!scope.isActive) return
searchBooks = copyDataS
searchBookLiveData.postValue(copyDataS)
}
@ -175,7 +159,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
* 停止搜索
*/
fun stop() {
task?.cancel()
searchBookModel.cancelSearch()
}
/**
@ -211,7 +195,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
override fun onCleared() {
super.onCleared()
searchPool.close()
searchBookModel.close()
}
}

@ -211,7 +211,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
}.onError {
finally(it.localizedMessage ?: "")
}.onSuccess {
finally(it ?: "导入完成")
finally(it)
}
}

@ -33,11 +33,9 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
updateList.remove(book.bookUrl)
postEvent(EventBus.UP_BOOK, book.bookUrl)
}
it?.let {
App.db.bookDao().update(book)
App.db.bookChapterDao().delByBook(book.bookUrl)
App.db.bookChapterDao().insert(*it.toTypedArray())
}
App.db.bookDao().update(book)
App.db.bookChapterDao().delByBook(book.bookUrl)
App.db.bookChapterDao().insert(*it.toTypedArray())
}
.onError {
synchronized(this) {

@ -43,10 +43,8 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
)
WebBook(bookSource).getBookInfo(book, this)
.onSuccess(IO) {
it?.let { book ->
App.db.bookDao().insert(book)
successCount++
}
App.db.bookDao().insert(it)
successCount++
}.onError {
throw Exception(it.localizedMessage)
}

@ -24,7 +24,7 @@ import org.jetbrains.anko.sdk27.listeners.onLongClick
class ExploreAdapter(context: Context, private val scope: CoroutineScope, val callBack: CallBack) :
SimpleRecyclerAdapter<BookSource>(context, R.layout.item_find_book) {
private var exIndex = 0
private var exIndex = -1
private var scrollTo = -1
override fun convert(holder: ItemViewHolder, item: BookSource, payloads: MutableList<Any>) {

@ -44,8 +44,8 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application
rssSource?.let { rssSource ->
Rss.getArticles(rssSource, null)
.onSuccess(IO) {
nextPageUrl = it?.nextPageUrl
it?.articles?.let { list ->
nextPageUrl = it.nextPageUrl
it.articles.let { list ->
list.forEach { rssArticle ->
rssArticle.order = order--
}
@ -76,8 +76,8 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application
if (source != null && !pageUrl.isNullOrEmpty()) {
Rss.getArticles(source, pageUrl)
.onSuccess(IO) {
nextPageUrl = it?.nextPageUrl
it?.articles?.let { list ->
nextPageUrl = it.nextPageUrl
it.articles.let { list ->
if (list.isEmpty()) {
callBack?.loadFinally(false)
return@let

@ -0,0 +1,17 @@
package io.legado.app.ui.rss.read
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.webkit.WebView
class VisibleWebView(
context: Context,
attrs: AttributeSet? = null
) : WebView(context, attrs) {
override fun onWindowVisibilityChanged(visibility: Int) {
super.onWindowVisibilityChanged(View.VISIBLE)
}
}

@ -187,7 +187,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
}.onError {
finally(it.localizedMessage ?: "")
}.onSuccess {
finally(it ?: "导入完成")
finally(it)
}
}

@ -0,0 +1,110 @@
package io.legado.app.ui.widget
import android.app.SearchableInfo
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.ImageSpan
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.widget.TextView
import androidx.appcompat.widget.SearchView
import io.legado.app.R
class SearchView : SearchView {
private var mSearchHintIcon: Drawable? = null
private var textView: TextView? = null
constructor(
context: Context?,
attrs: AttributeSet? = null
) : super(context, attrs)
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int
) : super(context, attrs, defStyleAttr)
override fun onLayout(
changed: Boolean,
left: Int,
top: Int,
right: Int,
bottom: Int
) {
super.onLayout(changed, left, top, right, bottom)
try {
if (textView == null) {
textView = findViewById(androidx.appcompat.R.id.search_src_text)
mSearchHintIcon = this.context.getDrawable(R.drawable.ic_search_hint)
updateQueryHint()
}
// 改变字体
textView!!.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
textView!!.gravity = Gravity.CENTER_VERTICAL
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun getDecoratedHint(hintText: CharSequence): CharSequence {
// If the field is always expanded or we don't have a search hint icon,
// then don't add the search icon to the hint.
if (mSearchHintIcon == null) {
return hintText
}
val textSize = (textView!!.textSize * 0.8).toInt()
mSearchHintIcon!!.setBounds(0, 0, textSize, textSize)
val ssb = SpannableStringBuilder(" ")
ssb.setSpan(CenteredImageSpan(mSearchHintIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
ssb.append(hintText)
return ssb
}
private fun updateQueryHint() {
textView?.let {
it.hint = getDecoratedHint(queryHint ?: "")
}
}
override fun setIconifiedByDefault(iconified: Boolean) {
super.setIconifiedByDefault(iconified)
updateQueryHint()
}
override fun setSearchableInfo(searchable: SearchableInfo?) {
super.setSearchableInfo(searchable)
searchable?.let {
updateQueryHint()
}
}
override fun setQueryHint(hint: CharSequence?) {
super.setQueryHint(hint)
updateQueryHint()
}
internal class CenteredImageSpan(drawable: Drawable?) : ImageSpan(drawable!!) {
override fun draw(
canvas: Canvas, text: CharSequence,
start: Int, end: Int, x: Float,
top: Int, y: Int, bottom: Int, paint: Paint
) {
// image to draw
val b = drawable
// font metrics of text to be replaced
val fm = paint.fontMetricsInt
val transY = ((y + fm.descent + y + fm.ascent) / 2
- b.bounds.bottom / 2)
canvas.save()
canvas.translate(x, transY.toFloat())
b.draw(canvas)
canvas.restore()
}
}
}

@ -0,0 +1,185 @@
package io.legado.app.ui.widget
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import android.widget.RelativeLayout
import io.legado.app.R
import io.legado.app.utils.getCompatColor
/**
* ShadowLayout.java
*
*
* Created by lijiankun on 17/8/11.
*/
class ShadowLayout(
context: Context,
attrs: AttributeSet? = null
) : RelativeLayout(context, attrs) {
private val mPaint =
Paint(Paint.ANTI_ALIAS_FLAG)
private val mRectF = RectF()
/**
* 阴影的颜色
*/
private var mShadowColor = Color.TRANSPARENT
/**
* 阴影的大小范围
*/
private var mShadowRadius = 0f
/**
* 阴影 x 轴的偏移量
*/
private var mShadowDx = 0f
/**
* 阴影 y 轴的偏移量
*/
private var mShadowDy = 0f
/**
* 阴影显示的边界
*/
private var mShadowSide = ALL
/**
* 阴影的形状圆形/矩形
*/
private var mShadowShape = SHAPE_RECTANGLE
init {
setLayerType(View.LAYER_TYPE_SOFTWARE, null) // 关闭硬件加速
setWillNotDraw(false) // 调用此方法后,才会执行 onDraw(Canvas) 方法
val typedArray =
context.obtainStyledAttributes(attrs, R.styleable.ShadowLayout)
mShadowColor = typedArray.getColor(
R.styleable.ShadowLayout_shadowColor,
context.getCompatColor(android.R.color.black)
)
mShadowRadius =
typedArray.getDimension(R.styleable.ShadowLayout_shadowRadius, dip2px(0f))
mShadowDx = typedArray.getDimension(R.styleable.ShadowLayout_shadowDx, dip2px(0f))
mShadowDy = typedArray.getDimension(R.styleable.ShadowLayout_shadowDy, dip2px(0f))
mShadowSide =
typedArray.getInt(R.styleable.ShadowLayout_shadowSide, ALL)
mShadowShape = typedArray.getInt(
R.styleable.ShadowLayout_shadowShape,
SHAPE_RECTANGLE
)
typedArray.recycle()
setUpShadowPaint()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val effect = mShadowRadius + dip2px(5f)
var rectLeft = 0f
var rectTop = 0f
var rectRight = this.measuredWidth.toFloat()
var rectBottom = this.measuredHeight.toFloat()
var paddingLeft = 0
var paddingTop = 0
var paddingRight = 0
var paddingBottom = 0
this.width
if (mShadowSide and LEFT == LEFT) {
rectLeft = effect
paddingLeft = effect.toInt()
}
if (mShadowSide and TOP == TOP) {
rectTop = effect
paddingTop = effect.toInt()
}
if (mShadowSide and RIGHT == RIGHT) {
rectRight = this.measuredWidth - effect
paddingRight = effect.toInt()
}
if (mShadowSide and BOTTOM == BOTTOM) {
rectBottom = this.measuredHeight - effect
paddingBottom = effect.toInt()
}
if (mShadowDy != 0.0f) {
rectBottom -= mShadowDy
paddingBottom += mShadowDy.toInt()
}
if (mShadowDx != 0.0f) {
rectRight -= mShadowDx
paddingRight += mShadowDx.toInt()
}
mRectF.left = rectLeft
mRectF.top = rectTop
mRectF.right = rectRight
mRectF.bottom = rectBottom
setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
}
/**
* 真正绘制阴影的方法
*/
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
setUpShadowPaint()
if (mShadowShape == SHAPE_RECTANGLE) {
canvas.drawRect(mRectF, mPaint)
} else if (mShadowShape == SHAPE_OVAL) {
canvas.drawCircle(
mRectF.centerX(),
mRectF.centerY(),
mRectF.width().coerceAtMost(mRectF.height()) / 2,
mPaint
)
}
}
fun setShadowColor(shadowColor: Int) {
mShadowColor = shadowColor
requestLayout()
postInvalidate()
}
fun setShadowRadius(shadowRadius: Float) {
mShadowRadius = shadowRadius
requestLayout()
postInvalidate()
}
private fun setUpShadowPaint() {
mPaint.reset()
mPaint.isAntiAlias = true
mPaint.color = Color.TRANSPARENT
mPaint.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor)
}
/**
* dip2px dp 值转 px
*
* @param dpValue dp
* @return px
*/
private fun dip2px(dpValue: Float): Float {
val dm = context.resources.displayMetrics
val scale = dm.density
return dpValue * scale + 0.5f
}
companion object {
const val ALL = 0x1111
const val LEFT = 0x0001
const val TOP = 0x0010
const val RIGHT = 0x0100
const val BOTTOM = 0x1000
const val SHAPE_RECTANGLE = 0x0001
const val SHAPE_OVAL = 0x0010
}
}

@ -37,6 +37,8 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex
init {
isPersistent = true
layoutResource = io.legado.app.R.layout.view_preference
val a = context.obtainStyledAttributes(attrs, R.styleable.ColorPreference)
showDialog = a.getBoolean(R.styleable.ColorPreference_cpv_showDialog, true)
@ -109,9 +111,12 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
val v = io.legado.app.ui.widget.prefs.Preference.bindView<ColorPanelView>(context, holder, icon, title, summary, widgetLayoutResource,
io.legado.app.R.id.cpv_preference_preview_color_panel, 30, 30)
if (v is ColorPanelView) {
v.color = color
}
super.onBindViewHolder(holder)
val preview = holder.itemView.findViewById(R.id.cpv_preference_preview_color_panel) as ColorPanelView
preview.color = color
}
override fun onSetInitialValue(defaultValue: Any?) {

@ -0,0 +1,21 @@
package io.legado.app.ui.widget.prefs
import android.content.Context
import android.util.AttributeSet
import android.widget.TextView
import androidx.preference.PreferenceViewHolder
import io.legado.app.R
class EditTextPreference(context: Context, attrs: AttributeSet) : androidx.preference.EditTextPreference(context, attrs) {
init {
// isPersistent = true
layoutResource = R.layout.view_preference
}
override fun onBindViewHolder(holder: PreferenceViewHolder?) {
Preference.bindView<TextView>(context, holder, icon, title, summary, null, null)
super.onBindViewHolder(holder)
}
}

@ -29,6 +29,7 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference
private val mEntryDrawables = arrayListOf<Drawable?>()
init {
layoutResource = R.layout.view_preference
widgetLayoutResource = R.layout.view_icon
val a = context.theme.obtainStyledAttributes(attrs, R.styleable.IconListPreference, 0, 0)
@ -52,11 +53,12 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference
override fun onBindViewHolder(holder: PreferenceViewHolder?) {
super.onBindViewHolder(holder)
holder?.itemView?.findViewById<ImageView>(R.id.preview)?.let {
val v = Preference.bindView<ImageView>(context, holder, icon, title, summary, widgetLayoutResource, R.id.preview, 50, 50)
if (v is ImageView) {
val selectedIndex = findIndexOfValue(value)
if (selectedIndex >= 0) {
val drawable = mEntryDrawables[selectedIndex]
it.setImageDrawable(drawable)
v.setImageDrawable(drawable)
}
}
}

@ -11,12 +11,15 @@ import io.legado.app.R
class NameListPreference(context: Context, attrs: AttributeSet) : ListPreference(context, attrs) {
init {
layoutResource = R.layout.view_preference
widgetLayoutResource = R.layout.item_fillet_text
}
override fun onBindViewHolder(holder: PreferenceViewHolder?) {
val v = Preference.bindView<TextView>(context, holder, icon, title, summary, widgetLayoutResource, R.id.text_view)
if (v is TextView) {
v.text = entry
}
super.onBindViewHolder(holder)
val textView = holder?.itemView?.findViewById<TextView>(R.id.text_view)
textView?.text = entry
}
}

@ -0,0 +1,85 @@
package io.legado.app.ui.widget.prefs
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.preference.PreferenceViewHolder
import io.legado.app.R
import io.legado.app.lib.theme.accentColor
import org.jetbrains.anko.layoutInflater
import kotlin.math.roundToInt
class Preference(context: Context, attrs: AttributeSet) : androidx.preference.Preference(context, attrs) {
init {
// isPersistent = true
layoutResource = R.layout.view_preference
}
companion object {
fun <T: View> bindView(context: Context, it: PreferenceViewHolder?, icon: Drawable?, title: CharSequence?, summary: CharSequence?, weightLayoutRes: Int?, viewId: Int?,
weightWidth: Int = 0, weightHeight: Int = 0): T? {
if (it == null) return null
val view = it.findViewById(R.id.preference_title)
if (view is TextView) {
view.text = title
view.isVisible = title != null && title.isNotEmpty()
val tvSummary = it.findViewById(R.id.preference_desc)
if (tvSummary is TextView) {
tvSummary.text = summary
tvSummary.isVisible = summary != null && summary.isNotEmpty()
}
val iconView = it.findViewById(R.id.preference_icon)
if (iconView is ImageView) {
iconView.isVisible = icon != null && icon.isVisible
iconView.setImageDrawable(icon)
iconView.setColorFilter(context.accentColor)
}
}
if (weightLayoutRes != null && weightLayoutRes != 0 && viewId != null && viewId != 0) {
val lay = it.findViewById(R.id.preference_widget)
if (lay is FrameLayout) {
var v = it.itemView.findViewById<T>(viewId)
if (v == null) {
val inflater: LayoutInflater = context.layoutInflater
val childView = inflater.inflate(weightLayoutRes, null)
lay.removeAllViews()
lay.addView(childView)
lay.isVisible = true
v = lay.findViewById(viewId)
}
if (weightWidth > 0 || weightHeight > 0) {
val lp = lay.layoutParams
if (weightHeight > 0)
lp.height = (context.resources.displayMetrics.density * weightHeight).roundToInt()
if (weightWidth > 0)
lp.width = (context.resources.displayMetrics.density * weightWidth).roundToInt()
lay.layoutParams = lp
}
return v
}
}
return null
}
}
override fun onBindViewHolder(holder: PreferenceViewHolder?) {
bindView<View>(context, holder, icon, title, summary, null, null)
super.onBindViewHolder(holder)
}
}

@ -1,22 +1,41 @@
package io.legado.app.ui.widget.prefs
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.View
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceViewHolder
import io.legado.app.R
import io.legado.app.lib.theme.accentColor
class PreferenceCategory(context: Context, attrs: AttributeSet) :
PreferenceCategory(context, attrs) {
class PreferenceCategory(context: Context, attrs: AttributeSet) : PreferenceCategory(context, attrs) {
init {
isPersistent = true
layoutResource = R.layout.view_preference_category
}
override fun onBindViewHolder(holder: PreferenceViewHolder?) {
super.onBindViewHolder(holder)
holder?.let {
val view = it.findViewById(android.R.id.title)
if (view is TextView && !view.isInEditMode) {
view.setTextColor(context.accentColor)//设置title文本的颜色
val view = it.findViewById(R.id.preference_title)
if (view is TextView) { // && !view.isInEditMode
view.text = title
view.setTextColor(context.accentColor) //设置title文本的颜色
view.isVisible = title != null && title.isNotEmpty()
val da = it.findViewById(R.id.preference_divider_above)
if (da is View) {
da.isVisible = it.isDividerAllowedAbove
}
val db = it.findViewById(R.id.preference_divider_below)
if (db is View) {
db.isVisible = it.isDividerAllowedBelow
}
}
}
}

@ -12,14 +12,24 @@ import io.legado.app.lib.theme.accentColor
class SwitchPreference(context: Context, attrs: AttributeSet) :
SwitchPreferenceCompat(context, attrs) {
init {
layoutResource = R.layout.view_preference
}
override fun onBindViewHolder(holder: PreferenceViewHolder?) {
super.onBindViewHolder(holder)
holder?.let {
val view = it.findViewById(R.id.switchWidget)
if (view is SwitchCompat) {
ATH.setTint(view, context.accentColor)
}
val v = Preference.bindView<SwitchCompat>(
context,
holder,
icon,
title,
summary,
widgetLayoutResource,
R.id.switchWidget
)
if (v is SwitchCompat) {
ATH.setTint(v, context.accentColor)
}
super.onBindViewHolder(holder)
}
}

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/btn_bg_press">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/background_prefs" />
<corners android:radius="0dp" />
</shape>
</item>
</ripple>

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,4.5A7.5,7.5 0,1 1,4.5 12,7.5 7.5,0 0,1 12,4.5M12,3a9,9 0,1 0,9 9,9 9,0 0,0 -9,-9Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M11.25,10.5h1.5v6h-1.5z"/>
<path
android:fillColor="#FF000000"
android:pathData="M12,8.5m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M9.94,4H5A2,2 0,0 0,3 6V18a2,2 0,0 0,2 2H19a2,2 0,0 0,2 -2V8a2,2 0,0 0,-2 -2H13.23a1.49,1.49 0,0 1,-1 -0.42L11,4.42a1.49,1.49 0,0 0,-1 -0.42ZM4.5,9.25V6A0.5,0.5 0,0 1,5 5.5H9.94l1.21,1.16a3,3 0,0 0,2.08 0.84H19a0.5,0.5 0,0 1,0.5 0.5V9.25ZM5,18.5a0.5,0.5 0,0 1,-0.5 -0.5V10.75h15V18a0.5,0.5 0,0 1,-0.5 0.5Z"/>
</vector>

@ -0,0 +1,5 @@
<vector android:height="32dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,4.5A7.5,7.5 0,1 1,4.5 12,7.5 7.5,0 0,1 12,4.5M12,3a9,9 0,1 0,9 9,9 9,0 0,0 -9,-9Z"/>
<path android:fillColor="#FF000000" android:pathData="M12,5.25v13.5a6.75,6.75 0,0 0,0 -13.5Z"/>
</vector>

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,4.5A7.5,7.5 0,1 1,4.5 12,7.5 7.5,0 0,1 12,4.5M12,3a9,9 0,1 0,9 9,9 9,0 0,0 -9,-9Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M15.01,7.25l-1.49,0l-1.51,2.59l-1.5,-2.59l-1.5,0l1.6,2.75l-1.6,0l0,1.5l2.24,0l0,1l-2.24,0l0,1.5l2.24,0l0,2.75l1.5,0l0,-2.75l2.24,0l0,-1.5l-2.24,0l0,-1l2.24,0l0,-1.5l-1.58,0l1.6,-2.75z"/>
</vector>

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,10.5A1.5,1.5 0,1 1,10.5 12,1.5 1.5,0 0,1 12,10.5M12,9a3,3 0,1 0,3 3,3 3,0 0,0 -3,-3Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M15.62,5.5h0L19.28,12l-3.66,6.5H8.38L4.72,12 8.38,5.5h7.24m0,-1.5H8.38a1.5,1.5 0,0 0,-1.31 0.77L3.41,11.26a1.55,1.55 0,0 0,0 1.48l3.66,6.49A1.5,1.5 0,0 0,8.38 20h7.24a1.5,1.5 0,0 0,1.31 -0.77l3.66,-6.49a1.55,1.55 0,0 0,0 -1.48L16.93,4.77A1.5,1.5 0,0 0,15.62 4Z"/>
</vector>

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M10.84,19.2V17.8a5.1,5.1 0,0 1,-5.13 -4.2,0.64 0.64,0 0,1 0.68,-0.7 0.87,0.87 0,0 1,0.82 0.69,3.66 3.66,0 0,0 3.63,2.81V15l3,2.1 -3,2.1Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M5.93,3 L3,10.52L4.61,10.52l0.62,-1.71h3l0.66,1.71h1.65L7.53,3ZM5.7,7.54l1,-2.79 1,2.79ZM20.63,17.69A2,2 0,0 0,19.57 17a1.76,1.76 0,0 0,0.78 -0.67,1.79 1.79,0 0,0 0,-1.84 2,2 0,0 0,-0.58 -0.63,1.91 1.91,0 0,0 -0.78,-0.31 8.48,8.48 0,0 0,-1.33 -0.08h-3L14.66,21h2.56c1,0 1.58,0 1.83,-0.05a2.21,2.21 0,0 0,1 -0.36,2 2,0 0,0 0.65,-0.77 2.23,2.23 0,0 0,0.24 -1A1.78,1.78 0,0 0,20.63 17.69ZM16.22,14.69h0.87c0.72,0 1.15,0 1.3,0A0.9,0.9 0,0 1,19 15a0.84,0.84 0,0 1,0.19 0.57,0.8 0.8,0 0,1 -0.22,0.59 1,1 0,0 1,-0.61 0.27c-0.14,0 -0.51,0 -1.1,0h-1ZM19.22,19.35a0.94,0.94 0,0 1,-0.55 0.3,7.41 7.41,0 0,1 -1,0L16.22,19.65v-2h1.23a5.11,5.11 0,0 1,1.34 0.11,1 1,0 0,1 0.48,0.34 1,1 0,0 1,0.16 0.58,0.93 0.93,0 0,1 -0.21,0.64Z"/>
</vector>

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M20.59,4.2A15,15 0,0 0,18.12 4,20 20,0 0,0 12,5 20,20 0,0 0,5.88 4a15,15 0,0 0,-2.47 0.2c-0.24,0 -0.41,0.25 -0.41,1.1L3,18.68a0.48,0.48 0,0 0,0.49 0.48h0.08A14.08,14.08 0,0 1,5.88 19a19.58,19.58 0,0 1,5.39 0.8L12,20l0.66,-0.2A19.85,19.85 0,0 1,18.12 19a14.08,14.08 0,0 1,2.31 0.18h0.08a0.48,0.48 0,0 0,0.49 -0.49L21,4.7A0.5,0.5 0,0 0,20.59 4.2ZM11.27,18.2a21.47,21.47 0,0 0,-5.39 -0.74c-0.48,0 -1,0 -1.42,0.06v-12A13.24,13.24 0,0 1,5.88 5.5a19,19 0,0 1,5.39 0.85ZM19.54,17.52c-0.46,0 -0.94,-0.06 -1.42,-0.06a21.27,21.27 0,0 0,-5.39 0.74L12.73,6.35a19.14,19.14 0,0 1,5.39 -0.85,13.11 13.11,0 0,1 1.42,0.07Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M18.12,8.45l0.5,0V7l-0.5,0a19.49,19.49 0,0 0,-4.5 0.56V9.05A18.3,18.3 0,0 1,18.12 8.45Z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M20.37,4.75 L16,3H14.29A2.5,2.5 0,0 1,9.71 3H8L3.63,4.75A1,1 0,0 0,3 5.68V10a1.05,1.05 0,0 0,1 1.05,1 1,0 0,0 0.3,-0.05L6.5,10v9a2,2 0,0 0,2 2h7a2,2 0,0 0,2 -2V10l2.18,1A1,1 0,0 0,21 10V5.68A1,1 0,0 0,20.37 4.75ZM19.5,9.27 L16.72,8a0.51,0.51 0,0 0,-0.72 0.46V19a0.5,0.5 0,0 1,-0.5 0.5h-7A0.5,0.5 0,0 1,8 19V8.45A0.5,0.5 0,0 0,7.29 8L4.5,9.27V6L8.29,4.5H8.9a4,4 0,0 0,6.2 0h0.61L19.5,6Z"/>
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M19.62,7.23A8.83,8.83 0,0 0,18.72 6,9 9,0 0,0 13.24,3.1 3.86,3.86 0,0 0,12.75 3c-0.25,0 -0.5,0 -0.75,0s-0.5,0 -0.75,0a3.86,3.86 0,0 0,-0.49 0.06A9,9 0,0 0,5.28 6a8.83,8.83 0,0 0,-0.9 1.21,8.93 8.93,0 0,0 0,9.54A8.83,8.83 0,0 0,5.28 18a9,9 0,0 0,5.48 2.92,3.86 3.86,0 0,0 0.49,0.06c0.25,0 0.5,0 0.75,0s0.5,0 0.75,0a3.86,3.86 0,0 0,0.49 -0.06A9,9 0,0 0,18.72 18a8.83,8.83 0,0 0,0.9 -1.21,8.93 8.93,0 0,0 0,-9.54ZM19.46,11.23L16,11.23A13.84,13.84 0,0 0,15.67 9a13.7,13.7 0,0 0,2.65 -1A7.36,7.36 0,0 1,19.46 11.25ZM17.37,6.77a12.57,12.57 0,0 1,-2.1 0.78A13.83,13.83 0,0 0,14 4.77,7.56 7.56,0 0,1 17.37,6.77ZM12,14.5a14.51,14.51 0,0 0,-2.2 0.19,12.41 12.41,0 0,1 -0.26,-1.94h4.92a12.41,12.41 0,0 1,-0.26 1.94A14.51,14.51 0,0 0,12 14.5ZM13.78,16.14A12.69,12.69 0,0 1,12 19.47a12.69,12.69 0,0 1,-1.78 -3.33A13,13 0,0 1,12 16,13 13,0 0,1 13.78,16.14ZM9.54,11.25A12.41,12.41 0,0 1,9.8 9.31,14.51 14.51,0 0,0 12,9.5a14.51,14.51 0,0 0,2.2 -0.19,12.41 12.41,0 0,1 0.26,1.94ZM10.22,7.86A12.69,12.69 0,0 1,12 4.53a12.69,12.69 0,0 1,1.78 3.33A13,13 0,0 1,12 8,13 13,0 0,1 10.22,7.86ZM10,4.77a13.83,13.83 0,0 0,-1.3 2.78,12.57 12.57,0 0,1 -2.1,-0.78A7.56,7.56 0,0 1,10 4.77ZM8.33,9A13.84,13.84 0,0 0,8 11.25L4.54,11.25A7.36,7.36 0,0 1,5.68 8,13.7 13.7,0 0,0 8.33,9ZM4.54,12.75L8,12.75A13.84,13.84 0,0 0,8.33 15a13.7,13.7 0,0 0,-2.65 1A7.36,7.36 0,0 1,4.54 12.75ZM6.63,17.23a12.57,12.57 0,0 1,2.1 -0.78A13.83,13.83 0,0 0,10 19.23,7.56 7.56,0 0,1 6.63,17.23ZM13.97,19.23a13.83,13.83 0,0 0,1.3 -2.78,12.57 12.57,0 0,1 2.1,0.78A7.56,7.56 0,0 1,14 19.23ZM15.67,15A13.84,13.84 0,0 0,16 12.75h3.5A7.36,7.36 0,0 1,18.32 16,13.7 13.7,0 0,0 15.67,15Z"/>
</vector>

@ -1,15 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="200"
android:viewportHeight="200">
<path
android:fillColor="#FF000000"
android:pathData="M100.1,181.4c-10.9,0 -21.5,-2.1 -31.4,-6.3c-9.6,-4.1 -18.2,-9.9 -25.7,-17.3s-13.2,-16 -17.3,-25.7c-4.2,-10 -6.3,-20.5 -6.3,-31.4c0,-10.9 2.1,-21.5 6.3,-31.4C29.8,59.7 35.6,51 43,43.6c7.4,-7.4 16,-13.2 25.7,-17.3c10,-4.2 20.5,-6.3 31.4,-6.3s21.5,2.1 31.4,6.3c9.6,4.1 18.2,9.9 25.7,17.3c7.4,7.4 13.2,16 17.3,25.7c4.2,10 6.3,20.5 6.3,31.4s-2.1,21.5 -6.3,31.4c-4.1,9.6 -9.9,18.2 -17.3,25.7c-7.4,7.4 -16,13.2 -25.7,17.3C121.6,179.3 111,181.4 100.1,181.4zM100.1,31.6C62,31.6 31,62.6 31,100.7c0,38.1 31,69.2 69.2,69.2s69.2,-31 69.2,-69.2C169.3,62.6 138.2,31.6 100.1,31.6L100.1,31.6z" />
<path
android:fillColor="#FF000000"
android:pathData="M114.1,78.8c0,-7.1 -4.2,-12.9 -14,-12.9c-6.3,0 -12.2,3.1 -18,9.2l-3.5,-3.3C84.8,65.5 91,61 100.6,61c12.1,0 19.2,7.3 19.2,17.4c0,15.1 -22.3,19 -19,34.4h-5.3C92,95.9 114.1,90.9 114.1,78.8zM98.5,122.6c3.3,0 6.4,2.3 6.4,6.4c0,4.2 -3.1,6.5 -6.4,6.5c-3.4,0 -6.4,-2.3 -6.4,-6.5C92,125 95.1,122.6 98.5,122.6z" />
<path
android:fillColor="#FF000000"
android:pathData="M98.5,138.1c-5.1,0 -8.9,-3.8 -8.9,-9c0,-5.1 3.7,-8.9 8.9,-8.9c5.1,0 8.9,3.8 8.9,8.9C107.4,134.9 102.8,138.1 98.5,138.1zM98.5,125.1c-1.8,0 -4,1 -4,4c0,3 2.2,4.1 4,4.1c2,0 4,-1.3 4,-4.1C102.5,126.3 100.4,125.1 98.5,125.1zM103.8,115.2H93.6l-0.4,-1.9c-1,-4.9 -0.2,-9.4 2.4,-13.6c2.2,-3.6 5.3,-6.5 8.3,-9.3c4.2,-3.9 7.9,-7.4 7.9,-11.6c0,-3.9 -1.5,-10.4 -11.5,-10.4c-5.5,0 -10.8,2.8 -16.2,8.4l-1.7,1.8l-7.1,-6.7l1.7,-1.8c6.8,-6.9 13.4,-11.5 23.7,-11.5c13,0 21.7,8 21.7,19.8c0,8.3 -5.7,13.6 -10.8,18.2c-5.2,4.8 -9.7,9 -8.3,15.6L103.8,115.2L103.8,115.2zM97.7,110.3h0.4c-0.3,-7.7 5.2,-12.8 10.1,-17.3c4.7,-4.4 9.2,-8.5 9.2,-14.6c0,-9.1 -6.6,-14.9 -16.8,-14.9c-0.1,0 -0.2,0 -0.3,0c10,0.1 16.2,5.9 16.2,15.3c0,6.3 -4.8,10.8 -9.4,15.2C102,98.7 97.1,103.3 97.7,110.3L97.7,110.3zM95.2,64c-3,0.7 -5.7,1.9 -8.2,3.7C89.7,65.9 92.4,64.7 95.2,64z" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#8F000000"
android:pathData="M474.84,721.55c0,21.68 17.55,38.71 38.71,38.71 21.68,0 38.71,-17.55 38.71,-38.71 0,-13.94 -7.23,-26.84 -19.61,-33.55 -11.87,-6.71 -26.84,-6.71 -38.71,0s-19.1,19.61 -19.1,33.55zM474.84,721.55M509.94,263.74C433.55,263.74 371.61,325.16 371.61,401.03c0,17.03 13.94,31.48 31.48,31.48 17.03,0 30.97,-13.94 31.48,-31.48 0,-41.81 33.03,-74.32 75.87,-74.32 41.81,0 75.87,34.06 75.87,76.39 0,16 -22.71,38.71 -42.84,58.84 -29.94,29.94 -64,64 -64,109.94v45.42c0.52,16.52 14.45,29.94 31.48,29.94s30.45,-13.42 30.97,-29.94v-45.42c0,-19.61 24.26,-44.39 45.94,-65.55 29.94,-29.94 60.9,-60.9 60.9,-102.71 -1.03,-77.42 -62.97,-139.87 -138.84,-139.87zM509.94,263.74"/>
<path
android:fillColor="#8F000000"
android:pathData="M512,157.42c195.61,0 354.06,158.97 354.58,354.58 0,195.61 -158.97,354.58 -354.58,354.58s-354.58,-158.97 -354.58,-354.58 159.48,-354.58 354.58,-354.58m0,-68.65c-233.81,0 -423.23,189.42 -423.23,423.23s189.42,423.23 423.23,423.23 423.23,-189.42 423.23,-423.23 -189.42,-423.23 -423.23,-423.23zM512,88.77"/>
</vector>

@ -7,49 +7,49 @@
<path
android:pathData="M0,0h108v108h-108z"
android:fillColor="#4d4d4d" />
<path android:pathData="M0,108H54V65c0,-12 -54,-5 -54,-5Z">
<path android:pathData="M0,108H54V65c0,-12 -54,-5 -54,-5Z">
<aapt:attr name="android:fillColor">
<gradient
<gradient
android:startY="79.9"
android:startX="0.62"
android:endY="88.57"
android:endX="53.06"
android:type="linear">
<item
android:offset="0"
android:color="#FFFFFFFF" />
<item
android:offset="1"
android:color="#FFCCCCCC" />
<item
android:offset="0"
android:color="#FFFFFFFF" />
<item
android:offset="1"
android:color="#FFCCCCCC" />
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M108,108H54V65c0,-12 54,-5 54,-5Z"
android:fillColor="#fff" />
<path android:pathData="M71.5,69 L78,75V58a94.89,94.89 0,0 0,-13 0.75V75Z">
android:fillColor="#f2f2f2" />
<path android:pathData="M71.5,69 L78,75V58a94.89,94.89 0,0 0,-13 0.75V75Z">
<aapt:attr name="android:fillColor">
<gradient
<gradient
android:startY="58.1"
android:startX="71.5"
android:endY="75"
android:endX="71.5"
android:type="linear">
<item
android:offset="0"
android:color="#FFFFFFFF" />
<item
android:offset="0.24"
android:color="#FFFFE1DD" />
<item
android:offset="0.58"
android:color="#FFFFBEB6" />
<item
android:offset="0.84"
android:color="#FFFFA89D" />
<item
android:offset="1"
android:color="#FFFFA094" />
<item
android:offset="0"
android:color="#FFFFFFFF" />
<item
android:offset="0.24"
android:color="#FFFFE1DD" />
<item
android:offset="0.58"
android:color="#FFFFBEB6" />
<item
android:offset="0.84"
android:color="#FFFFA89D" />
<item
android:offset="1"
android:color="#FFFFA094" />
</gradient>
</aapt:attr>
</path>

@ -0,0 +1,8 @@
<vector android:height="8dp"
android:viewportHeight="1024"
android:viewportWidth="1024"
android:width="8dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#c0c0c0" android:pathData="M462.43,894.32c-242.22,0 -439.3,-197.03 -439.3,-439.3S220.16,15.78 462.43,15.78s439.3,197.03 439.3,439.3 -197.07,439.25 -439.3,439.25zM462.43,62.32c-216.58,0 -392.75,176.17 -392.75,392.75s176.17,392.75 392.75,392.75c216.53,0 392.75,-176.17 392.75,-392.75s-176.22,-392.75 -392.75,-392.75z"/>
<path android:fillColor="#c0c0c0" android:pathData="M977.64,991.98a23.37,23.37 0,0 1,-16.48 -6.8l-211.22,-211.22a23.27,23.27 0,0 1,32.91 -32.91l211.22,211.22a23.27,23.27 0,0 1,-16.43 39.7z"/>
</vector>

@ -7,7 +7,7 @@
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="45dp"
android:elevation="10dp"
android:background="@color/background"
app:labelVisibilityMode="unlabeled"

@ -15,7 +15,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<WebView
<io.legado.app.ui.rss.read.VisibleWebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />

@ -4,64 +4,76 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:clickable="true"
android:focusable="true"
tools:ignore="UnusedAttribute">
android:paddingTop="2dp"
android:paddingRight="10dp"
android:paddingLeft="10dp">
<io.legado.app.ui.widget.image.CoverImageView
android:id="@+id/iv_cover"
<io.legado.app.ui.widget.ShadowLayout
android:id="@+id/bg_cover"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/img_cover"
android:scaleType="centerCrop"
android:src="@drawable/image_cover_default"
android:transitionName="img_cover"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_gravity="center_horizontal"
android:layout_marginTop="8dp"
tools:ignore="UnusedAttribute"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="UnusedAttribute" />
app:shadowColor="#16000000"
app:shadowShape="rectangle"
app:shadowDx="1dp"
app:shadowDy="3dp"
app:shadowRadius="4dp"
app:shadowSide="all">
<FrameLayout
android:id="@+id/fl_has_new"
<io.legado.app.ui.widget.image.CoverImageView
android:id="@+id/iv_cover"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@drawable/image_cover_default"
android:transitionName="img_cover"
tools:ignore="UnusedAttribute" />
</io.legado.app.ui.widget.ShadowLayout>
<io.legado.app.ui.widget.text.BadgeView
android:id="@+id/bv_unread"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="@id/iv_cover"
app:layout_constraintTop_toTopOf="@id/iv_cover">
<io.legado.app.ui.widget.text.BadgeView
android:id="@+id/bv_unread"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_margin="5dp"
android:includeFontPadding="false"
tools:ignore="RtlHardcoded" />
android:layout_gravity="right"
android:layout_margin="5dp"
android:includeFontPadding="false"
app:layout_constraintTop_toTopOf="@+id/bg_cover"
app:layout_constraintRight_toRightOf="@+id/bg_cover"
tools:ignore="RtlHardcoded" />
<io.legado.app.ui.widget.anima.RotateLoading
android:id="@+id/rl_loading"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_gravity="right"
android:visibility="invisible"
app:loading_width="2dp"
tools:ignore="RtlHardcoded" />
</FrameLayout>
<io.legado.app.ui.widget.anima.RotateLoading
android:id="@+id/rl_loading"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_gravity="right"
android:visibility="invisible"
app:layout_constraintTop_toTopOf="@+id/bg_cover"
app:layout_constraintRight_toRightOf="@+id/bg_cover"
app:loading_color="@color/colorAccent"
app:loading_width="2dp"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingBottom="4dp"
android:includeFontPadding="false"
android:gravity="center"
android:gravity="top|center_horizontal"
android:lines="2"
android:ellipsize="end"
android:text="@string/book_name"
android:textColor="@color/tv_text_default"
android:textSize="12sp"
app:layout_constraintLeft_toLeftOf="@+id/iv_cover"
app:layout_constraintRight_toRightOf="@+id/iv_cover"
app:layout_constraintTop_toBottomOf="@+id/iv_cover"
app:layout_constraintLeft_toLeftOf="@+id/bg_cover"
app:layout_constraintRight_toRightOf="@+id/bg_cover"
app:layout_constraintTop_toBottomOf="@+id/bg_cover"
tools:ignore="RtlHardcoded,RtlSymmetry" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/bg_prefs_color"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingTop="10dp"
android:paddingRight="16dp"
android:paddingBottom="4dp"
android:minHeight="42dp"
android:clickable="true"
android:orientation="horizontal"
android:focusable="true">
<ImageView
android:id="@+id/preference_icon"
android:layout_marginRight="16dp"
android:scaleType="fitCenter"
android:visibility="visible"
android:paddingTop="1dp"
android:layout_width="24dp"
android:layout_height="24dp"
tools:ignore="ContentDescription,RtlHardcoded" />
<LinearLayout
android:orientation="vertical"
android:layout_weight="1.0"
android:layout_width="0dp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/preference_title"
android:paddingBottom="8dp"
android:singleLine="true"
android:textSize="16sp"
android:text="@string/title"
android:textColor="@color/tv_text_default"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/preference_desc"
android:paddingBottom="8dp"
android:textSize="14sp"
android:textColor="@color/tv_text_summary"
android:text="@string/default1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<FrameLayout
android:id="@+id/preference_widget"
android:orientation="vertical"
android:layout_gravity="center"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:visibility="gone"
android:gravity="right|center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:ignore="RtlHardcoded" />
</LinearLayout>

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<View
android:id="@+id/preference_divider_above"
android:layout_width="match_parent"
android:layout_height="8dp" />
<TextView
android:id="@+id/preference_title"
android:paddingTop="16dip"
android:paddingBottom="8dip"
android:background="@drawable/bg_prefs_color"
android:paddingLeft="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<View
android:id="@+id/preference_divider_below"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_height="8dp" />
</LinearLayout>

@ -1,15 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.SearchView
<io.legado.app.ui.widget.SearchView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/search_view"
android:theme="?attr/actionBarStyle"
android:background="@drawable/bg_searchview"
android:layout_width="match_parent"
android:layout_height="34dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_height="30dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:imeOptions="actionSearch"
android:focusable="false"
app:queryBackground="@null"
app:submitBackground="@null"
app:searchHintIcon="@drawable/ic_search_hint"
app:defaultQueryHint="搜索"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

@ -7,6 +7,7 @@
<color name="background">@color/md_grey_900</color>
<color name="background_card">@color/md_grey_850</color>
<color name="background_menu">@color/md_grey_800</color>
<color name="background_prefs">#10303030</color>
<color name="night_mask">#69000000</color>

@ -173,4 +173,17 @@
<attr name="bgColor" format="color" />
<attr name="arcDirectionTop" format="boolean" />
</declare-styleable>
<declare-styleable name="ShadowLayout"><attr format="color" name="shadowColor"/><attr format="dimension" name="shadowRadius"/><attr format="dimension" name="shadowDx"/><attr format="dimension" name="shadowDy"/><attr name="shadowShape">
<flag name="rectangle" value="0x0001"/>
<flag name="oval" value="0x0010"/>
</attr><attr name="shadowSide">
<flag name="all" value="0x1111"/>
<flag name="left" value="0x0001"/>
<flag name="top" value="0x0010"/>
<flag name="right" value="0x0100"/>
<flag name="bottom" value="0x1000"/>
</attr>
</declare-styleable>
</resources>

@ -16,6 +16,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_200</color>
<color name="background_prefs">@color/md_grey_50</color>
<color name="transparent">#00000000</color>
<color name="transparent10">#10000000</color>

@ -29,6 +29,7 @@
<string name="delete">删除</string>
<string name="replace">替换</string>
<string name="replace_purify">替换净化</string>
<string name="replace_purify_desc">配置替换净化规则</string>
<string name="not_available">暂无</string>
<string name="enable">启用</string>
<string name="replace_purify_search">替换净化-搜索</string>
@ -58,6 +59,7 @@
<string name="book_local">添加本地</string>
<string name="book_source">书源</string>
<string name="book_source_manage">书源管理</string>
<string name="book_source_manage_desc">新建/导入/编辑/管理书源</string>
<string name="setting">设置</string>
<string name="theme_setting">主题设置</string>
<string name="theme_setting_s">与界面/颜色相关的一些设置</string>
@ -80,6 +82,7 @@
<string name="is_loading">加载中…</string>
<string name="retry">重试</string>
<string name="web_service">Web 服务</string>
<string name="web_service_desc">启用Web服务</string>
<string name="web_edit_source">web编辑书源</string>
<string name="http_ip">http://%1$s:%2$d</string>
<string name="download_offline">离线下载</string>
@ -442,6 +445,7 @@
<string name="click_on_selected_show_menu">选中时点击可弹出菜单</string>
<string name="theme">主题</string>
<string name="theme_mode">主题模式</string>
<string name="theme_mode_desc">选择主题模式</string>
<string name="default_theme">默认主题</string>
<string name="restore_default_theme">恢复主题为默认配色</string>
<string name="join_qq_group">加入QQ群</string>
@ -457,6 +461,7 @@
<string name="import_per">导入本地书籍需存储权限</string>
<string name="night_theme">夜间模式</string>
<string name="eink_theme">E-Ink 模式</string>
<string name="eink_theme_desc">电子墨水屏模式</string>
<string name="get_storage_per">本软件需要存储权限来存储备份书籍信息</string>
<string name="double_click_exit">再按一次退出程序</string>
<string name="import_book_per">导入本地书籍需存储权限</string>

@ -2,68 +2,78 @@
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="contributors"
android:title="开发人员"
android:summary="gedoor,Invinciblelee等, 详情请在github中查看"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="check_update"
android:title="@string/check_update"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="update_log"
android:title="@string/update_log"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="联系我们"
app:layout="@layout/view_preference_category"
app:allowDividerAbove="true"
app:allowDividerBelow="false"
app:iconSpaceReserved="false">
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="gzGzh"
android:title="关注公众号"
android:summary="开源阅读软件"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="qq"
android:title="@string/join_qq_group"
android:summary="点击加入"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="mail"
android:title="@string/send_mail"
android:summary="kunfei.ge@gmail.com"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="git"
android:title="@string/git_hub"
android:summary="@string/this_github_url"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="home_page"
android:title="@string/home_page"
android:summary="@string/home_page_url"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
</io.legado.app.ui.widget.prefs.PreferenceCategory>
<io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="其它"
app:layout="@layout/view_preference_category"
app:allowDividerAbove="true"
app:allowDividerBelow="false"
app:iconSpaceReserved="false">
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="license"
android:title="开源许可"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="disclaimer"
android:title="@string/disclaimer"
app:iconSpaceReserved="false" />

@ -4,51 +4,64 @@
<io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="微信"
app:layout="@layout/view_preference_category"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false">
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="gzGzh"
android:title="关注公众号"
android:summary="关注[开源阅读软件]点击广告支持我"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="wxZsm"
android:title="微信赞赏码"
android:summary="点击打开"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
</io.legado.app.ui.widget.prefs.PreferenceCategory>
<io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="支付宝"
app:layout="@layout/view_preference_category"
app:allowDividerAbove="true"
app:allowDividerBelow="false"
app:iconSpaceReserved="false">
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="zfbHbSsm"
android:title="支付宝红包搜索码"
android:summary="537954522 点击复制"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="zfbHbRwm"
android:title="支付宝红包二维码"
android:summary="点击打开"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="zfbSkRwm"
android:title="支付宝收款二维码"
android:summary="点击打开"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
</io.legado.app.ui.widget.prefs.PreferenceCategory>
<io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="QQ"
app:layout="@layout/view_preference_category"
app:allowDividerAbove="true"
app:allowDividerBelow="false"
app:iconSpaceReserved="false">
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="qqSkRwm"
android:title="QQ收款二维码"
android:summary="点击打开"

@ -4,21 +4,24 @@
<io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="@string/web_dav_set"
app:layout="@layout/view_preference_category"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false">
<androidx.preference.EditTextPreference
<io.legado.app.ui.widget.prefs.EditTextPreference
android:key="web_dav_url"
android:title="WebDav 服务器地址"
android:summary="@string/web_dav_url_s"
app:iconSpaceReserved="false" />
<androidx.preference.EditTextPreference
<io.legado.app.ui.widget.prefs.EditTextPreference
android:key="web_dav_account"
android:title="WebDav 账号"
android:summary="@string/web_dav_account_s"
app:iconSpaceReserved="false" />
<androidx.preference.EditTextPreference
<io.legado.app.ui.widget.prefs.EditTextPreference
android:key="web_dav_password"
android:title="WebDav 密码"
android:summary="@string/web_dav_pw_s"
@ -29,28 +32,33 @@
android:defaultValue="true"
android:title="创建子文件夹"
android:summary="创建legado文件夹作为备份文件夹"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
</io.legado.app.ui.widget.prefs.PreferenceCategory>
<io.legado.app.ui.widget.prefs.PreferenceCategory app:iconSpaceReserved="false">
<io.legado.app.ui.widget.prefs.PreferenceCategory
app:layout="@layout/view_preference_category"
app:allowDividerBelow="false"
app:iconSpaceReserved="false">
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="backupUri"
android:title="备份路径"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="web_dav_backup"
android:title="备份"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="web_dav_restore"
android:title="恢复"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="import_old"
android:title="导入旧版本数据"
app:iconSpaceReserved="false" />

@ -4,6 +4,9 @@
<io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="@string/main_activity"
app:layout="@layout/view_preference_category"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false">
<io.legado.app.ui.widget.prefs.SwitchPreference
@ -24,30 +27,35 @@
android:defaultValue="true"
android:key="showRss"
android:title="@string/show_rss"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
</io.legado.app.ui.widget.prefs.PreferenceCategory>
<io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="@string/other_setting"
app:layout="@layout/view_preference_category"
app:allowDividerAbove="true"
app:allowDividerBelow="false"
app:iconSpaceReserved="false">
<Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="threadCount"
android:title="@string/threads_num_title"
app:iconSpaceReserved="false" />
<Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="webPort"
android:title="@string/web_port_title"
app:iconSpaceReserved="false" />
<Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="downloadPath"
android:title="@string/download_path"
app:iconSpaceReserved="false" />
<Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="cleanCache"
android:title="@string/cleanCache"
app:iconSpaceReserved="false" />

@ -26,21 +26,26 @@
android:title="@string/navigation_bar_color_change"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:defaultValue="true"
android:key="barElevation"
android:summary="@string/bar_elevation_s"
android:title="@string/bar_elevation"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="defaultTheme"
android:summary="@string/restore_default_theme"
android:title="@string/default_theme"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="白天"
app:layout="@layout/view_preference_category"
app:allowDividerAbove="true"
app:allowDividerBelow="false"
app:iconSpaceReserved="false">
<io.legado.app.ui.widget.prefs.ColorPreference
@ -73,12 +78,17 @@
android:summary="白天,底栏色"
android:title="底部操作栏颜色"
app:cpv_dialogType="preset"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
</io.legado.app.ui.widget.prefs.PreferenceCategory>
<io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="夜间"
app:layout="@layout/view_preference_category"
app:allowDividerAbove="true"
app:allowDividerBelow="false"
app:iconSpaceReserved="false">
<io.legado.app.ui.widget.prefs.ColorPreference

@ -1,15 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:divider="@color/transparent"
app:allowDividerBelow="false"
app:allowDividerAbove="false"
>
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="bookSourceManage"
android:title="@string/book_source_manage"
android:summary="@string/book_source_manage_desc"
android:icon="@drawable/ic_cfg_source"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="replaceManage"
android:title="@string/replace_purify"
android:summary="@string/replace_purify_desc"
android:icon="@drawable/ic_cfg_replace"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.NameListPreference
@ -18,58 +26,76 @@
android:entryValues="@array/theme_mode_v"
android:key="themeMode"
android:title="@string/theme_mode"
android:summary="@string/theme_mode_desc"
android:icon="@drawable/ic_cfg_theme"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.SwitchPreference
android:defaultValue="false"
android:key="isEInkMode"
android:title="@string/eink_theme"
android:summary="@string/eink_theme_desc"
android:icon="@drawable/ic_cfg_e_lnk"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.SwitchPreference
android:defaultValue="false"
android:key="webService"
android:title="@string/web_service"
android:summary="@string/web_service_desc"
android:icon="@drawable/ic_cfg_web"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.PreferenceCategory
android:title="@string/setting"
app:iconSpaceReserved="false">
app:iconSpaceReserved="false"
app:allowDividerAbove="true"
app:allowDividerBelow="false" >
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="web_dav_setting"
android:summary="@string/web_dav_set_import_old"
android:title="@string/backup_restore"
android:icon="@drawable/ic_cfg_backup"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="theme_setting"
android:summary="@string/theme_setting_s"
android:title="@string/theme_setting"
android:icon="@drawable/ic_cfg_theme"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="setting"
android:summary="@string/other_setting_s"
android:title="@string/other_setting"
android:icon="@drawable/ic_cfg_other"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
</io.legado.app.ui.widget.prefs.PreferenceCategory>
<io.legado.app.ui.widget.prefs.PreferenceCategory
android:key="aboutCategory"
app:allowDividerAbove="true"
app:allowDividerBelow="false"
app:layout="@layout/view_preference_category"
app:iconSpaceReserved="false">
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="donate"
android:summary="您的支持是我更新的动力"
android:title="@string/donate"
android:icon="@drawable/ic_cfg_jz"
app:iconSpaceReserved="false" />
<androidx.preference.Preference
<io.legado.app.ui.widget.prefs.Preference
android:key="about"
android:summary="公众号[开源阅读软件]"
android:title="@string/about"
android:icon="@drawable/ic_cfg_about"
app:iconSpaceReserved="false" />
</io.legado.app.ui.widget.prefs.PreferenceCategory>

Loading…
Cancel
Save