pull/167/head
yangyxd 5 years ago
commit 74a435c4bd
  1. 1
      app/src/main/assets/updateLog.md
  2. 2
      app/src/main/java/io/legado/app/data/entities/Book.kt
  3. 34
      app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt
  4. 3
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  5. 1
      app/src/main/java/io/legado/app/help/storage/OldBook.kt
  6. 5
      app/src/main/java/io/legado/app/model/SearchBook.kt
  7. 5
      app/src/main/java/io/legado/app/model/WebBook.kt
  8. 32
      app/src/main/java/io/legado/app/model/webBook/BookList.kt
  9. 5
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt
  10. 5
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
  11. 2
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  12. 4
      app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt
  13. 2
      app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt
  14. 26
      app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt
  15. 2
      app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt
  16. 17
      app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt
  17. 11
      app/src/main/java/io/legado/app/ui/widget/SearchView.kt
  18. 2
      app/src/main/res/layout/activity_rss_read.xml
  19. 1
      app/src/main/res/layout/view_search.xml

@ -8,6 +8,7 @@
* 解决看过书籍的移到顶部需要向上滚动才能看到的bug * 解决看过书籍的移到顶部需要向上滚动才能看到的bug
* 只有再书源被删除找不到书源时才会自动换源 * 只有再书源被删除找不到书源时才会自动换源
* 美化界面by yangyxd * 美化界面by yangyxd
* 订阅后台播放
**2020/03/16** **2020/03/16**
* 修复滚动模式切换章节位置不归0的bug * 修复滚动模式切换章节位置不归0的bug

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

@ -6,7 +6,7 @@ import kotlin.coroutines.CoroutineContext
class Coroutine<T>( class Coroutine<T>(
scope: CoroutineScope, val scope: CoroutineScope,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
) { ) {
@ -31,6 +31,7 @@ class Coroutine<T>(
private var success: Callback<T?>? = null private var success: Callback<T?>? = null
private var error: Callback<Throwable>? = null private var error: Callback<Throwable>? = null
private var finally: VoidCallback? = null private var finally: VoidCallback? = null
private var cancel: VoidCallback? = null
private var timeMillis: Long? = null private var timeMillis: Long? = null
private var errorReturn: Result<T>? = null private var errorReturn: Result<T>? = null
@ -45,7 +46,7 @@ class Coroutine<T>(
get() = job.isCompleted get() = job.isCompleted
init { init {
this.job = executeInternal(scope, context, block) this.job = executeInternal(context, block)
} }
fun timeout(timeMillis: () -> Long): Coroutine<T> { fun timeout(timeMillis: () -> Long): Coroutine<T> {
@ -100,9 +101,28 @@ class Coroutine<T>(
return this@Coroutine 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) { fun cancel(cause: CancellationException? = null) {
job.cancel(cause) 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 { fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle {
@ -110,7 +130,6 @@ class Coroutine<T>(
} }
private fun executeInternal( private fun executeInternal(
scope: CoroutineScope,
context: CoroutineContext, context: CoroutineContext,
block: suspend CoroutineScope.() -> T block: suspend CoroutineScope.() -> T
): Job { ): Job {
@ -118,24 +137,30 @@ class Coroutine<T>(
try { try {
start?.let { dispatchVoidCallback(this, it) } start?.let { dispatchVoidCallback(this, it) }
val value = executeBlock(scope, context, timeMillis ?: 0L, block) val value = executeBlock(scope, context, timeMillis ?: 0L, block)
if (isActive) {
success?.let { dispatchCallback(this, value, it) } success?.let { dispatchCallback(this, value, it) }
}
} catch (e: Throwable) { } catch (e: Throwable) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
e.printStackTrace() e.printStackTrace()
} }
val consume: Boolean = errorReturn?.value?.let { value -> val consume: Boolean = errorReturn?.value?.let { value ->
if (isActive) {
success?.let { dispatchCallback(this, value, it) } success?.let { dispatchCallback(this, value, it) }
}
true true
} ?: false } ?: false
if (!consume) { if (!consume && isActive) {
error?.let { dispatchCallback(this, e, it) } error?.let { dispatchCallback(this, e, it) }
} }
} finally { } finally {
if (isActive) {
finally?.let { dispatchVoidCallback(this, it) } finally?.let { dispatchVoidCallback(this, it) }
} }
} }
} }
}
private suspend inline fun dispatchVoidCallback(scope: CoroutineScope, callback: VoidCallback) { private suspend inline fun dispatchVoidCallback(scope: CoroutineScope, callback: VoidCallback) {
if (null == callback.context) { if (null == callback.context) {
@ -152,6 +177,7 @@ class Coroutine<T>(
value: R, value: R,
callback: Callback<R> callback: Callback<R>
) { ) {
if (!scope.isActive) return
if (null == callback.context) { if (null == callback.context) {
callback.block.invoke(scope, value) callback.block.invoke(scope, value)
} else { } else {

@ -54,6 +54,7 @@ object Backup {
suspend fun backup(context: Context, path: String = legadoPath) { suspend fun backup(context: Context, path: String = legadoPath) {
context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis()) context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis())
withContext(IO) { withContext(IO) {
synchronized(this@Backup) {
writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath) writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath)
writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath) writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath)
writeListToJson(App.db.bookSourceDao().all, "bookSource.json", backupPath) writeListToJson(App.db.bookSourceDao().all, "bookSource.json", backupPath)
@ -86,6 +87,7 @@ object Backup {
} }
} }
} }
}
private fun writeListToJson(list: List<Any>, fileName: String, path: String) { private fun writeListToJson(list: List<Any>, fileName: String, path: String) {
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
@ -96,6 +98,7 @@ object Backup {
@Throws(java.lang.Exception::class) @Throws(java.lang.Exception::class)
private fun copyBackup(context: Context, uri: Uri) { private fun copyBackup(context: Context, uri: Uri) {
DocumentFile.fromTreeUri(context, uri)?.let { treeDoc -> DocumentFile.fromTreeUri(context, uri)?.let { treeDoc ->
for (fileName in backupFileNames) { for (fileName in backupFileNames) {
val file = File(backupPath + File.separator + fileName) val file = File(backupPath + File.separator + fileName)

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

@ -0,0 +1,5 @@
package io.legado.app.model
class SearchBook {
}

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

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

@ -51,7 +51,6 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
fun search() { fun search() {
task = execute { task = execute {
searchStateData.postValue(true)
val bookSourceList = App.db.bookSourceDao().allEnabled val bookSourceList = App.db.bookSourceDao().allEnabled
for (item in bookSourceList) { for (item in bookSourceList) {
//task取消时自动取消 by (scope = this@execute) //task取消时自动取消 by (scope = this@execute)
@ -72,6 +71,10 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
} }
} }
} }
}.onStart {
searchStateData.postValue(true)
}.onCancel {
searchStateData.postValue(false)
} }
task?.invokeOnCompletion { task?.invokeOnCompletion {

@ -72,7 +72,6 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
fun search() { fun search() {
task = execute { task = execute {
searchStateData.postValue(true)
val bookSourceList = App.db.bookSourceDao().allEnabled val bookSourceList = App.db.bookSourceDao().allEnabled
for (item in bookSourceList) { for (item in bookSourceList) {
//task取消时自动取消 by (scope = this@execute) //task取消时自动取消 by (scope = this@execute)
@ -95,6 +94,10 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
} }
} }
} }
}.onStart {
searchStateData.postValue(true)
}.onCancel {
searchStateData.postValue(false)
} }
task?.invokeOnCompletion { task?.invokeOnCompletion {

@ -161,7 +161,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
execute { execute {
App.db.bookSourceDao().allTextEnabled.forEach { source -> App.db.bookSourceDao().allTextEnabled.forEach { source ->
try { try {
val searchBooks = WebBook(source).searchBookSuspend(name) val searchBooks = WebBook(source).searchBookSuspend(this, name)
searchBooks.getOrNull(0)?.let { searchBooks.getOrNull(0)?.let {
if (it.name == name && (it.author == author || author == "")) { if (it.name == name && (it.author == author || author == "")) {
changeTo(it.toBook()) changeTo(it.toBook())

@ -50,8 +50,8 @@ class DiffCallBack(private val oldItems: List<SearchBook>, private val newItems:
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val payload = Bundle() val payload = Bundle()
val newItem: SearchBook? = if ((newItemPosition >= 0) && (newItemPosition < newItems.size)) newItems[newItemPosition] else null val newItem = newItems.getOrNull(newItemPosition)
val oldItem: SearchBook? = if ((oldItemPosition >= 0) && (oldItemPosition < oldItems.size)) oldItems[oldItemPosition] else null val oldItem = oldItems.getOrNull(oldItemPosition)
if (newItem == null) return payload if (newItem == null) return payload
if (oldItem?.name != newItem.name) { if (oldItem?.name != newItem.name) {
payload.putString("name", newItem.name) payload.putString("name", newItem.name)

@ -3,7 +3,6 @@ package io.legado.app.ui.book.search
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
@ -113,6 +112,7 @@ class SearchActivity : VMBaseActivity<SearchViewModel>(R.layout.activity_book_se
viewModel.saveSearchKey(query) viewModel.saveSearchKey(query)
viewModel.search(it) viewModel.search(it)
} }
openOrCloseHistory(false)
return true return true
} }

@ -12,8 +12,10 @@ import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.WebBook import io.legado.app.model.WebBook
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.isActive
import java.util.concurrent.Executors import java.util.concurrent.Executors
class SearchViewModel(application: Application) : BaseViewModel(application) { class SearchViewModel(application: Application) : BaseViewModel(application) {
@ -43,7 +45,6 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
searchKey = key searchKey = key
searchBooks.clear() searchBooks.clear()
} }
isSearchLiveData.postValue(true)
task = execute { task = execute {
val searchGroup = context.getPrefString("searchGroup") ?: "" val searchGroup = context.getPrefString("searchGroup") ?: ""
val bookSourceList = if (searchGroup.isBlank()) { val bookSourceList = if (searchGroup.isBlank()) {
@ -56,21 +57,28 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
WebBook(item).searchBook( WebBook(item).searchBook(
searchKey, searchKey,
searchPage, searchPage,
scope = this@execute, scope = this,
context = searchPool context = searchPool
) )
.timeout(30000L) .timeout(30000L)
.onSuccess(IO) { .onSuccess(IO) {
if (isActive) {
it?.let { list -> it?.let { list ->
if (context.getPrefBoolean(PreferKey.precisionSearch)) { if (context.getPrefBoolean(PreferKey.precisionSearch)) {
precisionSearch(list) precisionSearch(this, list)
} else { } else {
App.db.searchBookDao().insert(*list.toTypedArray()) App.db.searchBookDao().insert(*list.toTypedArray())
mergeItems(list) mergeItems(this, list)
}
} }
} }
} }
} }
}.onStart {
isSearchLiveData.postValue(true)
}.onCancel {
isSearchLiveData.postValue(false)
isLoading = false
} }
task?.invokeOnCompletion { task?.invokeOnCompletion {
@ -82,7 +90,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
/** /**
* 精确搜索处理 * 精确搜索处理
*/ */
private fun precisionSearch(searchBooks: List<SearchBook>) { private fun precisionSearch(scope: CoroutineScope, searchBooks: List<SearchBook>) {
val books = arrayListOf<SearchBook>() val books = arrayListOf<SearchBook>()
searchBooks.forEach { searchBook -> searchBooks.forEach { searchBook ->
if (searchBook.name.contains(searchKey, true) if (searchBook.name.contains(searchKey, true)
@ -90,14 +98,16 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
) books.add(searchBook) ) books.add(searchBook)
} }
App.db.searchBookDao().insert(*books.toTypedArray()) App.db.searchBookDao().insert(*books.toTypedArray())
mergeItems(books) if (scope.isActive) {
mergeItems(scope, books)
}
} }
/** /**
* 合并搜索结果并排序 * 合并搜索结果并排序
*/ */
@Synchronized @Synchronized
private fun mergeItems(newDataS: List<SearchBook>) { private fun mergeItems(scope: CoroutineScope, newDataS: List<SearchBook>) {
if (newDataS.isNotEmpty()) { if (newDataS.isNotEmpty()) {
val copyDataS = ArrayList(searchBooks) val copyDataS = ArrayList(searchBooks)
val searchBooksAdd = ArrayList<SearchBook>() val searchBooksAdd = ArrayList<SearchBook>()
@ -141,6 +151,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
} }
} }
} }
if (!scope.isActive) return
searchBooks.sortWith(Comparator { o1, o2 -> searchBooks.sortWith(Comparator { o1, o2 ->
if (o1.name == searchKey && o2.name != searchKey) { if (o1.name == searchKey && o2.name != searchKey) {
1 1
@ -166,6 +177,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) {
0 0
} }
}) })
if (!scope.isActive) return
searchBooks = copyDataS searchBooks = copyDataS
searchBookLiveData.postValue(copyDataS) searchBookLiveData.postValue(copyDataS)
} }

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

@ -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)
}
}

@ -11,7 +11,6 @@ import android.text.style.ImageSpan
import android.util.AttributeSet import android.util.AttributeSet
import android.util.TypedValue import android.util.TypedValue
import android.view.Gravity import android.view.Gravity
import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import io.legado.app.R import io.legado.app.R
@ -41,8 +40,7 @@ class SearchView : SearchView {
super.onLayout(changed, left, top, right, bottom) super.onLayout(changed, left, top, right, bottom)
try { try {
if (textView == null) { if (textView == null) {
textView = textView = findViewById(androidx.appcompat.R.id.search_src_text)
findViewById<View>(androidx.appcompat.R.id.search_src_text) as TextView
mSearchHintIcon = this.context.getDrawable(R.drawable.ic_search_hint) mSearchHintIcon = this.context.getDrawable(R.drawable.ic_search_hint)
updateQueryHint() updateQueryHint()
} }
@ -69,9 +67,8 @@ class SearchView : SearchView {
} }
private fun updateQueryHint() { private fun updateQueryHint() {
if (textView != null) { textView?.let {
val hint = queryHint it.hint = getDecoratedHint(queryHint ?: "")
textView!!.hint = getDecoratedHint(hint ?: "")
} }
} }
@ -91,7 +88,6 @@ class SearchView : SearchView {
super.setQueryHint(hint) super.setQueryHint(hint)
updateQueryHint() updateQueryHint()
} }
}
internal class CenteredImageSpan(drawable: Drawable?) : ImageSpan(drawable!!) { internal class CenteredImageSpan(drawable: Drawable?) : ImageSpan(drawable!!) {
override fun draw( override fun draw(
@ -111,3 +107,4 @@ internal class CenteredImageSpan(drawable: Drawable?) : ImageSpan(drawable!!) {
canvas.restore() canvas.restore()
} }
} }
}

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

@ -14,5 +14,4 @@
app:queryBackground="@null" app:queryBackground="@null"
app:submitBackground="@null" app:submitBackground="@null"
app:searchHintIcon="@drawable/ic_search_hint" app:searchHintIcon="@drawable/ic_search_hint"
app:goIcon="@drawable/ic_search"
app:defaultQueryHint="搜索"/> app:defaultQueryHint="搜索"/>
Loading…
Cancel
Save