diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 482907c7c..d6a435788 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -8,6 +8,7 @@ * 解决看过书籍的移到顶部需要向上滚动才能看到的bug * 只有再书源被删除找不到书源时才会自动换源 * 美化界面by yangyxd +* 订阅后台播放 **2020/03/16** * 修复滚动模式切换章节位置不归0的bug diff --git a/app/src/main/java/io/legado/app/data/entities/Book.kt b/app/src/main/java/io/legado/app/data/entities/Book.kt index 9e50a6d4c..0dd5cab9e 100644 --- a/app/src/main/java/io/legado/app/data/entities/Book.kt +++ b/app/src/main/java/io/legado/app/data/entities/Book.kt @@ -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(), // 最近一次更新书籍信息的时间 diff --git a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt index e9fd0df86..eb0a29375 100644 --- a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt +++ b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt @@ -6,7 +6,7 @@ import kotlin.coroutines.CoroutineContext class Coroutine( - scope: CoroutineScope, + val scope: CoroutineScope, context: CoroutineContext = Dispatchers.IO, block: suspend CoroutineScope.() -> T ) { @@ -31,6 +31,7 @@ class Coroutine( private var success: Callback? = null private var error: Callback? = null private var finally: VoidCallback? = null + private var cancel: VoidCallback? = null private var timeMillis: Long? = null private var errorReturn: Result? = null @@ -45,7 +46,7 @@ class Coroutine( get() = job.isCompleted init { - this.job = executeInternal(scope, context, block) + this.job = executeInternal(context, block) } fun timeout(timeMillis: () -> Long): Coroutine { @@ -100,9 +101,28 @@ class Coroutine( return this@Coroutine } + fun onCancel( + context: CoroutineContext? = null, + block: suspend CoroutineScope.() -> Unit + ): Coroutine { + 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( } private fun executeInternal( - scope: CoroutineScope, context: CoroutineContext, block: suspend CoroutineScope.() -> T ): Job { @@ -118,21 +137,27 @@ class Coroutine( 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( value: R, callback: Callback ) { + if (!scope.isActive) return if (null == callback.context) { callback.block.invoke(scope, value) } else { diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index 8c4fa8fc2..32bc2b765 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -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) diff --git a/app/src/main/java/io/legado/app/help/storage/OldBook.kt b/app/src/main/java/io/legado/app/help/storage/OldBook.kt index f8321973c..a782cf5f5 100644 --- a/app/src/main/java/io/legado/app/help/storage/OldBook.kt +++ b/app/src/main/java/io/legado/app/help/storage/OldBook.kt @@ -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 diff --git a/app/src/main/java/io/legado/app/model/SearchBook.kt b/app/src/main/java/io/legado/app/model/SearchBook.kt new file mode 100644 index 000000000..7b344e60f --- /dev/null +++ b/app/src/main/java/io/legado/app/model/SearchBook.kt @@ -0,0 +1,5 @@ +package io.legado.app.model + +class SearchBook { + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/WebBook.kt b/app/src/main/java/io/legado/app/model/WebBook.kt index 7d14ea8bc..6fee7add6 100644 --- a/app/src/main/java/io/legado/app/model/WebBook.kt +++ b/app/src/main/java/io/legado/app/model/WebBook.kt @@ -29,11 +29,12 @@ class WebBook(val bookSource: BookSource) { context: CoroutineContext = Dispatchers.IO ): Coroutine> { return Coroutine.async(scope, context) { - searchBookSuspend(key, page) + searchBookSuspend(scope, key, page) } } suspend fun searchBookSuspend( + scope: CoroutineScope, key: String, page: Int? = 1 ): ArrayList { @@ -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, diff --git a/app/src/main/java/io/legado/app/model/webBook/BookList.kt b/app/src/main/java/io/legado/app/model/webBook/BookList.kt index a205c950a..372a4eec6 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookList.kt @@ -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()) { diff --git a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt index 627bacc09..bcd0e61d0 100644 --- a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt @@ -51,7 +51,6 @@ 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) @@ -72,6 +71,10 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application } } } + }.onStart { + searchStateData.postValue(true) + }.onCancel { + searchStateData.postValue(false) } task?.invokeOnCompletion { diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt index e0800c0a1..5b1c7e939 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt @@ -72,7 +72,6 @@ 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) @@ -95,6 +94,10 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio } } } + }.onStart { + searchStateData.postValue(true) + }.onCancel { + searchStateData.postValue(false) } task?.invokeOnCompletion { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 25d94bdea..2994dcac2 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -161,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()) diff --git a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt index d0c2f2973..999f2a941 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt @@ -50,8 +50,8 @@ class DiffCallBack(private val oldItems: List, private val newItems: override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { val payload = Bundle() - val newItem: SearchBook? = if ((newItemPosition >= 0) && (newItemPosition < newItems.size)) newItems[newItemPosition] else null - val oldItem: SearchBook? = if ((oldItemPosition >= 0) && (oldItemPosition < oldItems.size)) oldItems[oldItemPosition] else null + val newItem = newItems.getOrNull(newItemPosition) + val oldItem = oldItems.getOrNull(oldItemPosition) if (newItem == null) return payload if (oldItem?.name != newItem.name) { payload.putString("name", newItem.name) diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt index 4373dd41a..e81545adc 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt @@ -3,7 +3,6 @@ package io.legado.app.ui.book.search import android.os.Bundle import android.view.Menu import android.view.MenuItem -import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import androidx.appcompat.widget.SearchView @@ -113,6 +112,7 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se viewModel.saveSearchKey(query) viewModel.search(it) } + openOrCloseHistory(false) return true } diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index 47671626d..f1a4fc858 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -12,8 +12,10 @@ import io.legado.app.help.coroutine.Coroutine import io.legado.app.model.WebBook import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefString +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.isActive import java.util.concurrent.Executors class SearchViewModel(application: Application) : BaseViewModel(application) { @@ -43,7 +45,6 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { searchKey = key searchBooks.clear() } - isSearchLiveData.postValue(true) task = execute { val searchGroup = context.getPrefString("searchGroup") ?: "" val bookSourceList = if (searchGroup.isBlank()) { @@ -54,23 +55,30 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { for (item in bookSourceList) { //task取消时自动取消 by (scope = this@execute) WebBook(item).searchBook( - searchKey, - searchPage, - scope = this@execute, - context = searchPool - ) + searchKey, + searchPage, + scope = this, + 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) + if (isActive) { + it?.let { list -> + if (context.getPrefBoolean(PreferKey.precisionSearch)) { + precisionSearch(this, list) + } else { + App.db.searchBookDao().insert(*list.toTypedArray()) + mergeItems(this, list) + } } } } } + }.onStart { + isSearchLiveData.postValue(true) + }.onCancel { + isSearchLiveData.postValue(false) + isLoading = false } task?.invokeOnCompletion { @@ -82,7 +90,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { /** * 精确搜索处理 */ - private fun precisionSearch(searchBooks: List) { + private fun precisionSearch(scope: CoroutineScope, searchBooks: List) { val books = arrayListOf() searchBooks.forEach { searchBook -> if (searchBook.name.contains(searchKey, true) @@ -90,14 +98,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) { + private fun mergeItems(scope: CoroutineScope, newDataS: List) { if (newDataS.isNotEmpty()) { val copyDataS = ArrayList(searchBooks) val searchBooksAdd = ArrayList() @@ -141,6 +151,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 +177,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { 0 } }) + if (!scope.isActive) return searchBooks = copyDataS searchBookLiveData.postValue(copyDataS) } diff --git a/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt b/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt index faf9cef8a..ccfa5bf82 100644 --- a/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt @@ -24,7 +24,7 @@ import org.jetbrains.anko.sdk27.listeners.onLongClick class ExploreAdapter(context: Context, private val scope: CoroutineScope, val callBack: CallBack) : SimpleRecyclerAdapter(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) { diff --git a/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt b/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt new file mode 100644 index 000000000..0e6e1b40b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt @@ -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) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/SearchView.kt b/app/src/main/java/io/legado/app/ui/widget/SearchView.kt index a1d8bd5f6..765a0a580 100644 --- a/app/src/main/java/io/legado/app/ui/widget/SearchView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/SearchView.kt @@ -11,7 +11,6 @@ import android.text.style.ImageSpan import android.util.AttributeSet import android.util.TypedValue import android.view.Gravity -import android.view.View import android.widget.TextView import androidx.appcompat.widget.SearchView import io.legado.app.R @@ -41,8 +40,7 @@ class SearchView : SearchView { super.onLayout(changed, left, top, right, bottom) try { if (textView == null) { - textView = - findViewById(androidx.appcompat.R.id.search_src_text) as TextView + textView = findViewById(androidx.appcompat.R.id.search_src_text) mSearchHintIcon = this.context.getDrawable(R.drawable.ic_search_hint) updateQueryHint() } @@ -69,9 +67,8 @@ class SearchView : SearchView { } private fun updateQueryHint() { - if (textView != null) { - val hint = queryHint - textView!!.hint = getDecoratedHint(hint ?: "") + textView?.let { + it.hint = getDecoratedHint(queryHint ?: "") } } @@ -91,23 +88,23 @@ class SearchView : SearchView { 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() + 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() + } } -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/activity_rss_read.xml b/app/src/main/res/layout/activity_rss_read.xml index 76f630002..ea4131764 100644 --- a/app/src/main/res/layout/activity_rss_read.xml +++ b/app/src/main/res/layout/activity_rss_read.xml @@ -15,7 +15,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> - diff --git a/app/src/main/res/layout/view_search.xml b/app/src/main/res/layout/view_search.xml index b1809c534..96ff311da 100644 --- a/app/src/main/res/layout/view_search.xml +++ b/app/src/main/res/layout/view_search.xml @@ -14,5 +14,4 @@ app:queryBackground="@null" app:submitBackground="@null" app:searchHintIcon="@drawable/ic_search_hint" - app:goIcon="@drawable/ic_search" app:defaultQueryHint="搜索"/> \ No newline at end of file