Преглед изворни кода

加载字数选项
优化数据保存类型,方便未来按字数过滤
根据选项自动决定是否加载正文

Jason Yao пре 4 година
родитељ
комит
399ed44b5c

+ 1 - 0
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"

+ 3 - 2
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() {

+ 5 - 3
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

+ 6 - 1
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<SearchBook> {
 
     @Ignore

+ 6 - 0
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) {

+ 31 - 4
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()
             }
         }
     }

+ 8 - 1
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<BookSourceActivity>()
             R.id.menu_refresh_list -> viewModel.startRefreshList()

+ 56 - 12
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<BookChapter>
+    ) = 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<SearchBook> = 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) {

+ 30 - 1
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" />
 
+    <TextView
+        android:id="@+id/tv_current_chapter_word_count"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="30dp"
+        android:layout_marginBottom="10dp"
+        android:singleLine="false"
+        tools:text="word count"
+        android:textColor="@color/secondaryText"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/tv_last" />
+
+    <TextView
+        android:id="@+id/tv_respond_time"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="30dp"
+        android:layout_marginBottom="10dp"
+        android:singleLine="false"
+        tools:text="respond time"
+        android:textColor="@color/secondaryText"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/tv_current_chapter_word_count" />
+
     <androidx.appcompat.widget.AppCompatImageView
         android:id="@+id/iv_checked"
         android:layout_width="40dp"

+ 6 - 0
app/src/main/res/menu/change_source.xml

@@ -34,6 +34,12 @@
         android:checkable="true"
         app:showAsAction="never" />
 
+    <item
+        android:id="@+id/menu_load_word_count"
+        android:title="@string/load_word_count"
+        android:checkable="true"
+        app:showAsAction="never" />
+
     <item
         android:id="@+id/menu_load_info"
         android:title="@string/load_info"

+ 2 - 0
app/src/main/res/values-es-rES/strings.xml

@@ -874,6 +874,7 @@
     <string name="upload_url">上传URL</string>
     <string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
     <string name="sort_by_respondTime">Ordenar por tiempo de respuesta</string>
+    <string name="respondTime">tiempo de respuesta: %1$d ms</string>
     <string name="export_success">导出成功</string>
     <string name="path">路径</string>
     <string name="direct_link_upload_rule">直链上传规则</string>
@@ -1099,4 +1100,5 @@
     <string name="only_latest_backup_t">仅保留最新备份</string>
     <string name="only_latest_backup_s">本地备份仅保留最新备份文件</string>
     <string name="webdav_application_authorization_error">Fail to authorize WebDav application</string>
+    <string name="load_word_count">Cargar palabras del libro</string>
 </resources>

+ 2 - 0
app/src/main/res/values-ja-rJP/strings.xml

@@ -877,6 +877,7 @@
     <string name="upload_url">上传URL</string>
     <string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
     <string name="sort_by_respondTime">Sort by respond time</string>
+    <string name="respondTime">respondTime: %1$d ms</string>
     <string name="export_success">导出成功</string>
     <string name="path">路径</string>
     <string name="direct_link_upload_rule">直链上传规则</string>
@@ -1102,4 +1103,5 @@
     <string name="only_latest_backup_t">仅保留最新备份</string>
     <string name="only_latest_backup_s">本地备份仅保留最新备份文件</string>
     <string name="webdav_application_authorization_error">Fail to authorize WebDav application</string>
+    <string name="load_word_count">Load word count</string>
 </resources>

+ 2 - 0
app/src/main/res/values-pt-rBR/strings.xml

@@ -875,6 +875,7 @@
     <string name="upload_url">上传URL</string>
     <string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
     <string name="sort_by_respondTime">Classificar por tempo de resposta</string>
+    <string name="respondTime">tempo de resposta: %1$d ms</string>
     <string name="export_success">导出成功</string>
     <string name="path">路径</string>
     <string name="direct_link_upload_rule">直链上传规则</string>
@@ -1102,4 +1103,5 @@
     <string name="only_latest_backup_t">仅保留最新备份</string>
     <string name="only_latest_backup_s">本地备份仅保留最新备份文件</string>
     <string name="webdav_application_authorization_error">Fail to authorize WebDav application</string>
+    <string name="load_word_count">Carregar os palavras do livro</string>
 </resources>

+ 2 - 0
app/src/main/res/values-zh-rHK/strings.xml

@@ -874,6 +874,7 @@
     <string name="upload_url">上傳URL</string>
     <string name="download_url_rule">下载URL规则(downloadUrls)</string>
     <string name="sort_by_respondTime">響應時間排序</string>
+    <string name="respondTime">響應時間:%1$d ms</string>
     <string name="export_success">導出成功</string>
     <string name="path">路徑</string>
     <string name="direct_link_upload_rule">直鏈上傳規則</string>
@@ -1099,4 +1100,5 @@
     <string name="only_latest_backup_t">仅保留最新备份</string>
     <string name="only_latest_backup_s">本地备份仅保留最新备份文件</string>
     <string name="webdav_application_authorization_error">webDav应用验证失败</string>
+    <string name="load_word_count">加載字數</string>
 </resources>

+ 2 - 0
app/src/main/res/values-zh-rTW/strings.xml

@@ -876,6 +876,7 @@
     <string name="upload_url">上傳URL</string>
     <string name="download_url_rule">下载URL规则(downloadUrls)</string>
     <string name="sort_by_respondTime">反應時間排序</string>
+    <string name="respondTime">反應時間:%1$d ms</string>
     <string name="export_success">匯出成功</string>
     <string name="path">路徑</string>
     <string name="direct_link_upload_rule">直鏈上傳規則</string>
@@ -1101,4 +1102,5 @@
     <string name="only_latest_backup_t">仅保留最新备份</string>
     <string name="only_latest_backup_s">本地备份仅保留最新备份文件</string>
     <string name="webdav_application_authorization_error">webDav应用验证失败</string>
+    <string name="load_word_count">加載字數</string>
 </resources>

+ 2 - 0
app/src/main/res/values-zh/strings.xml

@@ -878,6 +878,7 @@
     <string name="upload_url">上传 URL</string>
     <string name="download_url_rule">下载URL规则(downloadUrls)</string>
     <string name="sort_by_respondTime">响应时间排序</string>
+    <string name="respondTime">响应时间:%1$d ms</string>
     <string name="export_success">导出成功</string>
     <string name="path">路径</string>
     <string name="direct_link_upload_rule">直链上传规则</string>
@@ -1101,4 +1102,5 @@
     <string name="only_latest_backup_t">仅保留最新备份</string>
     <string name="only_latest_backup_s">本地备份仅保留最新备份文件</string>
     <string name="webdav_application_authorization_error">webDav应用验证失败</string>
+    <string name="load_word_count">加载字数</string>
 </resources>

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -1102,4 +1102,6 @@
     <string name="only_latest_backup_t">仅保留最新备份</string>
     <string name="only_latest_backup_s">本地备份仅保留最新备份文件</string>
     <string name="webdav_application_authorization_error">Fail to authorize WebDav application</string>
+    <string name="respondTime">respondTime: %1$d ms</string>
+    <string name="load_word_count">Load word count</string>
 </resources>