diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index 969a7ca69..069ed7b99 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -61,6 +61,7 @@ object PreferKey { const val changeSourceCheckAuthor = "changeSourceCheckAuthor" const val changeSourceLoadToc = "changeSourceLoadToc" const val changeSourceLoadInfo = "changeSourceLoadInfo" + const val changeSourceLoadWordCount = "changeSourceLoadWordCount" const val chineseConverterType = "chineseConverterType" const val launcherIcon = "launcherIcon" const val textSelectAble = "selectText" diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index 6c9c51ee2..5b67a16c6 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -21,7 +21,7 @@ val appDb by lazy { } @Database( - version = 66, + version = 67, exportSchema = true, entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class, ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class, @@ -51,7 +51,8 @@ val appDb by lazy { AutoMigration(from = 62, to = 63), AutoMigration(from = 63, to = 64), AutoMigration(from = 64, to = 65, spec = DatabaseMigrations.Migration_64_65::class), - AutoMigration(from = 65, to = 66) + AutoMigration(from = 65, to = 66), + AutoMigration(from = 66, to = 67) ] ) abstract class AppDatabase : RoomDatabase() { diff --git a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt index 9f9e8ba44..9444a0964 100644 --- a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt @@ -15,7 +15,7 @@ interface SearchBookDao { @Query( """select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, - t1.wordCount, t2.customOrder as originOrder + t1.wordCount, t2.customOrder as originOrder, t1.chapterWordCountText, t1.respondTime, t1.chapterWordCount from searchBooks as t1 inner join book_sources as t2 on t1.origin = t2.bookSourceUrl where t1.name = :name and t1.author like '%'||:author||'%' @@ -27,7 +27,7 @@ interface SearchBookDao { @Query( """select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, - t1.wordCount, t2.customOrder as originOrder + t1.wordCount, t2.customOrder as originOrder, t1.chapterWordCountText, t1.respondTime, t1.chapterWordCount from searchBooks as t1 inner join book_sources as t2 on t1.origin = t2.bookSourceUrl where t1.name = :name and t1.author like '%'||:author||'%' @@ -45,7 +45,9 @@ interface SearchBookDao { @Query( """ - select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder + select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, + t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, + t1.wordCount, t2.customOrder as originOrder, t1.chapterWordCountText, t1.respondTime, t1.chapterWordCount from searchBooks as t1 inner join book_sources as t2 on t1.origin = t2.bookSourceUrl where t1.name = :name and t1.author = :author and t1.coverUrl is not null and t1.coverUrl <> '' and t2.enabled = 1 diff --git a/app/src/main/java/io/legado/app/data/entities/SearchBook.kt b/app/src/main/java/io/legado/app/data/entities/SearchBook.kt index a343bdac6..bf284aae2 100644 --- a/app/src/main/java/io/legado/app/data/entities/SearchBook.kt +++ b/app/src/main/java/io/legado/app/data/entities/SearchBook.kt @@ -41,7 +41,12 @@ data class SearchBook( var tocUrl: String = "", var time: Long = System.currentTimeMillis(), override var variable: String? = null, - var originOrder: Int = 0 + var originOrder: Int = 0, + var chapterWordCountText: String? = null, + @ColumnInfo(defaultValue = "-1") + var chapterWordCount: Int = -1, + @ColumnInfo(defaultValue = "-1") + var respondTime: Int = -1 ) : Parcelable, BaseBook, Comparable { @Ignore diff --git a/app/src/main/java/io/legado/app/help/config/AppConfig.kt b/app/src/main/java/io/legado/app/help/config/AppConfig.kt index 9bf74fd08..b00b5f496 100644 --- a/app/src/main/java/io/legado/app/help/config/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/AppConfig.kt @@ -330,6 +330,12 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener { appCtx.putPrefBoolean(PreferKey.changeSourceLoadToc, value) } + var changeSourceLoadWordCount: Boolean + get() = appCtx.getPrefBoolean(PreferKey.changeSourceLoadWordCount) + set(value) { + appCtx.putPrefBoolean(PreferKey.changeSourceLoadWordCount, value) + } + var contentSelectSpeakMod: Int get() = appCtx.getPrefInt(PreferKey.contentSelectSpeakMod) set(value) { diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceAdapter.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceAdapter.kt index 5306611de..fa0cdf0ac 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceAdapter.kt @@ -13,6 +13,7 @@ import io.legado.app.base.adapter.DiffRecyclerAdapter import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.data.entities.SearchBook import io.legado.app.databinding.ItemChangeSourceBinding +import io.legado.app.help.config.AppConfig import io.legado.app.utils.getCompatColor import io.legado.app.utils.gone import io.legado.app.utils.invisible @@ -55,6 +56,8 @@ class ChangeBookSourceAdapter( tvOrigin.text = item.originName tvAuthor.text = item.author tvLast.text = item.getDisplayLastChapterTitle() + tvCurrentChapterWordCount.text = item.chapterWordCountText + tvRespondTime.text = context.getString(R.string.respondTime, item.respondTime) if (callBack.oldBookUrl == item.bookUrl) { ivChecked.visible() } else { @@ -82,13 +85,37 @@ class ChangeBookSourceAdapter( } else if (score < 0) { binding.ivGood.gone() binding.ivBad.visible() - DrawableCompat.setTint(binding.ivGood.drawable, appCtx.getCompatColor(R.color.md_red_100)) - DrawableCompat.setTint(binding.ivBad.drawable, appCtx.getCompatColor(R.color.md_blue_A200)) + DrawableCompat.setTint( + binding.ivGood.drawable, + appCtx.getCompatColor(R.color.md_red_100) + ) + DrawableCompat.setTint( + binding.ivBad.drawable, + appCtx.getCompatColor(R.color.md_blue_A200) + ) } else { binding.ivGood.visible() binding.ivBad.visible() - DrawableCompat.setTint(binding.ivGood.drawable, appCtx.getCompatColor(R.color.md_red_100)) - DrawableCompat.setTint(binding.ivBad.drawable, appCtx.getCompatColor(R.color.md_blue_100)) + DrawableCompat.setTint( + binding.ivGood.drawable, + appCtx.getCompatColor(R.color.md_red_100) + ) + DrawableCompat.setTint( + binding.ivBad.drawable, + appCtx.getCompatColor(R.color.md_blue_100) + ) + } + + if (AppConfig.changeSourceLoadWordCount && !item.chapterWordCountText.isNullOrBlank()) { + tvCurrentChapterWordCount.visible() + } else { + tvCurrentChapterWordCount.gone() + } + + if (AppConfig.changeSourceLoadWordCount && item.respondTime >= 0) { + tvRespondTime.visible() + } else { + tvRespondTime.gone() } } } diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt index 13f4fb6b0..0c3389a8a 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt @@ -9,9 +9,9 @@ import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar import androidx.core.os.bundleOf import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle.State.STARTED import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import androidx.lifecycle.Lifecycle.State.STARTED import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.legado.app.R @@ -111,6 +111,8 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_ ?.isChecked = AppConfig.changeSourceLoadInfo binding.toolBar.menu.findItem(R.id.menu_load_toc) ?.isChecked = AppConfig.changeSourceLoadToc + binding.toolBar.menu.findItem(R.id.menu_load_word_count) + ?.isChecked = AppConfig.changeSourceLoadWordCount } private fun initRecyclerView() { @@ -218,6 +220,11 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_ AppConfig.changeSourceLoadToc = !item.isChecked item.isChecked = !item.isChecked } + R.id.menu_load_word_count -> { + AppConfig.changeSourceLoadWordCount = !item.isChecked + item.isChecked = !item.isChecked + viewModel.onLoadWordCountChecked(item.isChecked) + } R.id.menu_start_stop -> viewModel.startOrStopSearch() R.id.menu_source_manage -> startActivity() R.id.menu_refresh_list -> viewModel.startRefreshList() diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt index a2ad805eb..a58113ced 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt @@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope import io.legado.app.base.BaseViewModel import io.legado.app.constant.AppConst import io.legado.app.constant.AppPattern +import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.data.appDb import io.legado.app.data.entities.Book @@ -19,17 +20,17 @@ import io.legado.app.help.config.AppConfig import io.legado.app.help.config.SourceConfig import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.help.coroutine.Coroutine +import io.legado.app.model.ReadBook import io.legado.app.model.webBook.WebBook import io.legado.app.utils.getPrefBoolean +import io.legado.app.utils.postEvent import io.legado.app.utils.toastOnUi +import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.ExecutorCoroutineDispatcher -import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.withContext import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executors @@ -146,6 +147,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a stopSearch() appDb.searchBookDao.clear(name, author) searchBooks.clear() + searchCallback?.upAdapter() bookSourceList.clear() val searchGroup = AppConfig.searchGroup if (searchGroup.isBlank()) { @@ -182,14 +184,17 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a if ((AppConfig.changeSourceCheckAuthor && searchBook.author.contains(author)) || !AppConfig.changeSourceCheckAuthor ) { - if (searchBook.latestChapterTitle.isNullOrEmpty()) { - if (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) { + when { + searchBook.latestChapterTitle.isNullOrEmpty() && + (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) -> { loadBookInfo(source, searchBook.toBook()) - } else { + } + searchBook.chapterWordCountText.isNullOrBlank() && AppConfig.changeSourceLoadWordCount -> { + loadBookToc(source, searchBook.toBook()) + } + else -> { searchCallback?.searchSuccess(searchBook) } - } else { - searchCallback?.searchSuccess(searchBook) } } } @@ -219,7 +224,37 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a val chapters = WebBook.getChapterListAwait(source, book).getOrThrow() tocMap[book.bookUrl] = chapters bookMap[book.bookUrl] = book - val searchBook: SearchBook = book.toSearchBook() + if (context.getPrefBoolean(PreferKey.changeSourceLoadWordCount)) { + loadBookWordCount(source, book, chapters) + } else { + val searchBook = book.toSearchBook() + searchCallback?.searchSuccess(searchBook) + } + } + + private suspend fun loadBookWordCount( + source: BookSource, + book: Book, + chapters: List + ) = coroutineScope { + val chapterIndex = ReadBook.curTextChapter?.chapter?.index ?: (chapters.size - 1) + val bookChapter = chapters.getOrNull(chapterIndex) + val startTime = System.currentTimeMillis() + val pair = try { + if (bookChapter == null) throw NoStackTraceException("章节缺失,总章节数${chapters.size}") + if (!isActive) return@coroutineScope + WebBook.getContentAwait(source, book, bookChapter, null, false).length.let { + it to "第${chapterIndex + 1}章 字数:${it}" + } + } catch (t: Throwable) { + -1 to "第${chapterIndex + 1}章 获取字数失败:${t.localizedMessage}" + } + val endTime = System.currentTimeMillis() + val searchBook = book.toSearchBook().apply { + chapterWordCountText = pair.second + chapterWordCount = pair.first + respondTime = (endTime - startTime).toInt() + } searchCallback?.searchSuccess(searchBook) } @@ -240,15 +275,24 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a } } + fun onLoadWordCountChecked(isChecked: Boolean) { + postEvent(EventBus.SOURCE_CHANGED, "") + if (isChecked) { + startRefreshList(searchBooks.filter { it.chapterWordCountText == null }) + } + } + /** * 刷新列表 */ - fun startRefreshList() { + fun startRefreshList(refreshList: List = searchBooks) { execute { + if (refreshList.isEmpty()) return@execute stopSearch() searchBookList.clear() - searchBookList.addAll(searchBooks) - searchBooks.clear() + searchBookList.addAll(refreshList) + searchBooks.removeAll(refreshList) + searchCallback?.upAdapter() searchStateData.postValue(true) initSearchPool() for (i in 0 until threadCount) { diff --git a/app/src/main/res/layout/item_change_source.xml b/app/src/main/res/layout/item_change_source.xml index 7605978e8..4a84f9692 100644 --- a/app/src/main/res/layout/item_change_source.xml +++ b/app/src/main/res/layout/item_change_source.xml @@ -42,6 +42,7 @@ android:layout_marginTop="10dp" android:singleLine="true" android:textColor="@color/primaryText" + tools:text="bookSourceName" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/tv_author" app:layout_constraintTop_toTopOf="parent" /> @@ -53,6 +54,7 @@ android:layout_marginTop="10dp" android:maxWidth="160dp" android:singleLine="true" + tools:text="author" android:textColor="@color/secondaryText" app:layout_constraintRight_toLeftOf="@+id/iv_checked" app:layout_constraintTop_toTopOf="parent" /> @@ -64,12 +66,39 @@ android:layout_marginLeft="30dp" android:layout_marginBottom="10dp" android:singleLine="true" + tools:text="latest chapter name" android:textColor="@color/secondaryText" - app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/iv_checked" app:layout_constraintTop_toBottomOf="@+id/tv_origin" /> + + + + + + 上传URL downloadUrlRule(downloadUrls) Ordenar por tiempo de respuesta + tiempo de respuesta: %1$d ms 导出成功 路径 直链上传规则 @@ -1099,4 +1100,5 @@ 仅保留最新备份 本地备份仅保留最新备份文件 Fail to authorize WebDav application + Cargar palabras del libro diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 0f839858e..405da1bd4 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -877,6 +877,7 @@ 上传URL downloadUrlRule(downloadUrls) Sort by respond time + respondTime: %1$d ms 导出成功 路径 直链上传规则 @@ -1102,4 +1103,5 @@ 仅保留最新备份 本地备份仅保留最新备份文件 Fail to authorize WebDav application + Load word count diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index f79f90f4b..a9d08590a 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -875,6 +875,7 @@ 上传URL downloadUrlRule(downloadUrls) Classificar por tempo de resposta + tempo de resposta: %1$d ms 导出成功 路径 直链上传规则 @@ -1102,4 +1103,5 @@ 仅保留最新备份 本地备份仅保留最新备份文件 Fail to authorize WebDav application + Carregar os palavras do livro diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 3951662d1..b52a94550 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -874,6 +874,7 @@ 上傳URL 下载URL规则(downloadUrls) 響應時間排序 + 響應時間:%1$d ms 導出成功 路徑 直鏈上傳規則 @@ -1099,4 +1100,5 @@ 仅保留最新备份 本地备份仅保留最新备份文件 webDav应用验证失败 + 加載字數 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 985e78636..365484f36 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -876,6 +876,7 @@ 上傳URL 下载URL规则(downloadUrls) 反應時間排序 + 反應時間:%1$d ms 匯出成功 路徑 直鏈上傳規則 @@ -1101,4 +1102,5 @@ 仅保留最新备份 本地备份仅保留最新备份文件 webDav应用验证失败 + 加載字數 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 571053fd5..3c2bd6d67 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -878,6 +878,7 @@ 上传 URL 下载URL规则(downloadUrls) 响应时间排序 + 响应时间:%1$d ms 导出成功 路径 直链上传规则 @@ -1101,4 +1102,5 @@ 仅保留最新备份 本地备份仅保留最新备份文件 webDav应用验证失败 + 加载字数 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bec4bd4fa..4665aea79 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1102,4 +1102,6 @@ 仅保留最新备份 本地备份仅保留最新备份文件 Fail to authorize WebDav application + respondTime: %1$d ms + Load word count