加载字数选项
优化数据保存类型,方便未来按字数过滤 根据选项自动决定是否加载正文
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user