Merge pull request #3063 from h11128/check-source-content

添加换源加载字数选项,显示当前章节或者最新章节字数
This commit is contained in:
kunfei 2023-05-08 08:44:38 +08:00 committed by GitHub
commit 58a9bcaee1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 166 additions and 24 deletions

View File

@ -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"

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"

View File

@ -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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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