Merge remote-tracking branch 'origin/master'

This commit is contained in:
kunfei
2023-05-09 08:11:09 +08:00
19 changed files with 2100 additions and 118 deletions
@@ -19,9 +19,11 @@ object SourceHelp {
private val list18Plus by lazy {
try {
return@lazy String(appCtx.assets.open("18PlusList.txt").readBytes())
.splitNotBlank("\n")
.splitNotBlank("\n").map {
EncoderUtils.base64Decode(it)
}.toHashSet()
} catch (e: Exception) {
return@lazy arrayOf<String>()
return@lazy hashSetOf<String>()
}
}
@@ -32,42 +34,42 @@ object SourceHelp {
}
fun insertRssSource(vararg rssSources: RssSource) {
rssSources.forEach { rssSource ->
if (is18Plus(rssSource.sourceUrl)) {
handler.post {
appCtx.toastOnUi("${rssSource.sourceName}是18+网址,禁止导入.")
}
} else {
appDb.rssSourceDao.insert(rssSource)
val rssSourcesGroup = rssSources.groupBy {
is18Plus(it.sourceUrl)
}
rssSourcesGroup[true]?.forEach {
handler.post {
appCtx.toastOnUi("${it.sourceName}是18+网址,禁止导入.")
}
}
rssSourcesGroup[false]?.let {
appDb.rssSourceDao.insert(*it.toTypedArray())
}
}
fun insertBookSource(vararg bookSources: BookSource) {
bookSources.forEach { bookSource ->
if (is18Plus(bookSource.bookSourceUrl)) {
handler.post {
appCtx.toastOnUi("${bookSource.bookSourceName}是18+网址,禁止导入.")
}
} else {
appDb.bookSourceDao.insert(bookSource)
val bookSourcesGroup = bookSources.groupBy {
is18Plus(it.bookSourceUrl)
}
bookSourcesGroup[true]?.forEach {
handler.post {
appCtx.toastOnUi("${it.bookSourceName}是18+网址,禁止导入.")
}
}
bookSourcesGroup[false]?.let {
appDb.bookSourceDao.insert(*it.toTypedArray())
}
}
private fun is18Plus(url: String?): Boolean {
url ?: return false
val baseUrl = NetworkUtils.getBaseUrl(url)
baseUrl ?: return false
if (AppConst.isPlayChannel) return false
val baseUrl = NetworkUtils.getBaseUrl(url) ?: return false
kotlin.runCatching {
val host = baseUrl.split("//", ".")
val base64Url = EncoderUtils.base64Encode("${host[host.lastIndex - 1]}.${host.last()}")
list18Plus.forEach {
if (base64Url == it) {
return true
}
val host = baseUrl.split("//", ".").let {
if (it.size > 2) "${it[it.lastIndex - 1]}.${it.last()}" else return false
}
return list18Plus.contains(host)
}
return false
}
@@ -307,7 +307,6 @@ object ReadBook : CoroutineScope by MainScope() {
contentLoadFinish(book, chapter, it, upContent, resetPageOffset) {
success?.invoke()
}
removeLoading(chapter.index)
} ?: download(this, chapter, resetPageOffset = resetPageOffset)
} ?: removeLoading(index)
}.onError {
@@ -320,26 +319,24 @@ object ReadBook : CoroutineScope by MainScope() {
/**
* 下载正文
*/
private fun download(index: Int) {
private fun downloadIndex(index: Int) {
if (index < 0) return
if (index > chapterSize - 1) {
upToc()
return
}
book?.let { book ->
if (book.isLocal) return
if (addLoading(index)) {
Coroutine.async {
appDb.bookChapterDao.getChapter(book.bookUrl, index)?.let { chapter ->
if (BookHelp.hasContent(book, chapter)) {
removeLoading(chapter.index)
} else {
download(this, chapter, false)
}
} ?: removeLoading(index)
}.onError {
removeLoading(index)
}
val book = book ?: return
if (addLoading(index)) {
try {
appDb.bookChapterDao.getChapter(book.bookUrl, index)?.let { chapter ->
if (BookHelp.hasContent(book, chapter)) {
removeLoading(chapter.index)
} else {
download(this, chapter, false)
}
} ?: removeLoading(index)
} catch (e: Exception) {
removeLoading(index)
}
}
}
@@ -353,20 +350,17 @@ object ReadBook : CoroutineScope by MainScope() {
resetPageOffset: Boolean,
success: (() -> Unit)? = null
) {
val book = book
val book = book ?: return removeLoading(chapter.index)
val bookSource = bookSource
if (book != null && bookSource != null) {
if (bookSource != null) {
CacheBook.getOrCreate(bookSource, book).download(scope, chapter)
} else if (book != null) {
} else {
val msg = if (book.isLocal) "无内容" else "没有书源"
contentLoadFinish(
book, chapter, "加载正文失败\n$msg", resetPageOffset = resetPageOffset
) {
success?.invoke()
}
removeLoading(chapter.index)
} else {
removeLoading(chapter.index)
}
}
@@ -415,10 +409,12 @@ object ReadBook : CoroutineScope by MainScope() {
curPageChanged()
callBack?.contentLoadFinish()
}
-1 -> {
prevTextChapter = textChapter
if (upContent) callBack?.upContent(offset, resetPageOffset)
}
1 -> {
nextTextChapter = textChapter
if (upContent) callBack?.upContent(offset, resetPageOffset)
@@ -464,18 +460,17 @@ object ReadBook : CoroutineScope by MainScope() {
fun saveRead() {
Coroutine.async {
book?.let { book ->
book.lastCheckCount = 0
book.durChapterTime = System.currentTimeMillis()
book.durChapterIndex = durChapterIndex
book.durChapterPos = durChapterPos
appDb.bookChapterDao.getChapter(book.bookUrl, durChapterIndex)?.let {
book.durChapterTitle = it.getDisplayTitle(
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()
)
}
appDb.bookDao.update(book)
val book = book ?: return@async
book.lastCheckCount = 0
book.durChapterTime = System.currentTimeMillis()
book.durChapterIndex = durChapterIndex
book.durChapterPos = durChapterPos
appDb.bookChapterDao.getChapter(book.bookUrl, durChapterIndex)?.let {
book.durChapterTitle = it.getDisplayTitle(
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()
)
}
appDb.bookDao.update(book)
}
}
@@ -483,6 +478,7 @@ object ReadBook : CoroutineScope by MainScope() {
* 预下载
*/
private fun preDownload() {
if (book?.isLocal == true) return
if (AppConfig.preDownloadNum < 2) {
return
}
@@ -491,12 +487,12 @@ object ReadBook : CoroutineScope by MainScope() {
val maxChapterIndex = durChapterIndex + AppConfig.preDownloadNum
for (i in durChapterIndex.plus(2)..maxChapterIndex) {
delay(1000)
download(i)
downloadIndex(i)
}
val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum)
for (i in durChapterIndex.minus(2) downTo minChapterIndex) {
delay(1000)
download(i)
downloadIndex(i)
}
}
}
@@ -430,7 +430,7 @@ class AudioPlayService : BaseService(),
private fun upMediaSessionPlaybackState(state: Int) {
mediaSessionCompat?.setPlaybackState(
PlaybackStateCompat.Builder()
.setActions(MediaHelp.MEDIA_SESSION_ACTIONS)
.setActions(PlaybackStateCompat.ACTION_SEEK_TO)
.setState(state, exoPlayer.currentPosition, 1f)
.setBufferedPosition(exoPlayer.bufferedPosition)
.build()
@@ -27,6 +27,7 @@ import io.legado.app.databinding.DialogBookChangeSourceBinding
import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.source.manage.BookSourceActivity
import io.legado.app.ui.widget.dialog.WaitDialog
@@ -86,7 +87,7 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
binding.toolBar.setBackgroundColor(primaryColor)
viewModel.initData(arguments)
viewModel.initData(arguments, callBack?.oldBook, activity is ReadBookActivity)
showTitle()
initMenu()
initRecyclerView()
@@ -212,19 +213,23 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
item.isChecked = !item.isChecked
viewModel.refresh()
}
R.id.menu_load_info -> {
AppConfig.changeSourceLoadInfo = !item.isChecked
item.isChecked = !item.isChecked
}
R.id.menu_load_toc -> {
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()
@@ -306,7 +311,7 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
}
override fun setBookScore(searchBook: SearchBook, score: Int) {
viewModel.setBookScore(searchBook,score)
viewModel.setBookScore(searchBook, score)
}
override fun getBookScore(searchBook: SearchBook): Int {
@@ -16,12 +16,14 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.book.BookHelp
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.ui.book.read.ReadBookActivity
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.postEvent
import io.legado.app.utils.toastOnUi
@@ -44,6 +46,8 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
var searchFinishCallback: ((isEmpty: Boolean) -> Unit)? = null
var name: String = ""
var author: String = ""
private var fromReadBookActivity = false
private var oldBook: Book? = null
private var tasks = CompositeCoroutine()
private var screenKey: String = ""
private var bookSourceList = arrayListOf<BookSource>()
@@ -114,7 +118,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
}
@CallSuper
open fun initData(arguments: Bundle?) {
open fun initData(arguments: Bundle?, book: Book?, fromReadBookActivity: Boolean) {
arguments?.let { bundle ->
bundle.getString("name")?.let {
name = it
@@ -122,6 +126,8 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
bundle.getString("author")?.let {
author = it.replace(AppPattern.authorRegex, "")
}
this.fromReadBookActivity = fromReadBookActivity
oldBook = book
}
}
@@ -180,22 +186,24 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
val task = Coroutine.async(scope = viewModelScope, context = searchPool!!) {
val resultBooks = WebBook.searchBookAwait(source, name)
resultBooks.forEach { searchBook ->
if (searchBook.name == name) {
if ((AppConfig.changeSourceCheckAuthor && searchBook.author.contains(author))
|| !AppConfig.changeSourceCheckAuthor
) {
when {
searchBook.latestChapterTitle.isNullOrEmpty() &&
(AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) -> {
loadBookInfo(source, searchBook.toBook())
}
searchBook.chapterWordCountText.isNullOrBlank() && AppConfig.changeSourceLoadWordCount -> {
loadBookToc(source, searchBook.toBook())
}
else -> {
searchCallback?.searchSuccess(searchBook)
}
}
if (searchBook.name != name) {
return@forEach
}
if (AppConfig.changeSourceCheckAuthor && !searchBook.author.contains(author)) {
return@forEach
}
when {
searchBook.latestChapterTitle.isNullOrEmpty() &&
(AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) -> {
loadBookInfo(source, searchBook.toBook())
}
AppConfig.changeSourceLoadWordCount -> {
loadBookToc(source, searchBook.toBook())
}
else -> {
searchCallback?.searchSuccess(searchBook)
}
}
}
@@ -211,7 +219,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
private suspend fun loadBookInfo(source: BookSource, book: Book) {
WebBook.getBookInfoAwait(source, book)
if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) {
if (AppConfig.changeSourceLoadToc || AppConfig.changeSourceLoadWordCount) {
loadBookToc(source, book)
} else {
//从详情页里获取最新章节
@@ -224,7 +232,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
val chapters = WebBook.getChapterListAwait(source, book).getOrThrow()
tocMap[book.bookUrl] = chapters
bookMap[book.bookUrl] = book
if (context.getPrefBoolean(PreferKey.changeSourceLoadWordCount)) {
if (AppConfig.changeSourceLoadWordCount) {
loadBookWordCount(source, book, chapters)
} else {
val searchBook = book.toSearchBook()
@@ -237,16 +245,26 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
book: Book,
chapters: List<BookChapter>
) = coroutineScope {
val chapterIndex = ReadBook.curTextChapter?.chapter?.index ?: (chapters.size - 1)
val chapterIndex = if (fromReadBookActivity) {
oldBook?.let {
BookHelp.getDurChapter(
it.durChapterIndex,
it.durChapterTitle,
chapters,
it.totalChapterNum
)
} ?: chapters.lastIndex
} else chapters.lastIndex
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 {
val nextChapterUrl = chapters.getOrNull(chapterIndex + 1)?.url
WebBook.getContentAwait(source, book, bookChapter, nextChapterUrl, false).length.let {
it to "${chapterIndex + 1}章 字数:${it}"
}
} catch (t: Throwable) {
if (t is CancellationException) throw t
-1 to "${chapterIndex + 1}章 获取字数失败:${t.localizedMessage}"
}
val endTime = System.currentTimeMillis()
@@ -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 ChangeChapterSourceAdapter(
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 ChangeChapterSourceAdapter(
} 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()
}
}
}
@@ -2,16 +2,20 @@ package io.legado.app.ui.book.changesource
import android.content.DialogInterface
import android.os.Bundle
import android.view.*
import android.view.KeyEvent
import android.view.KeyEvent.ACTION_UP
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
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
@@ -28,11 +32,21 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.elevation
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.source.manage.BookSourceActivity
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.*
import io.legado.app.utils.StartActivityContract
import io.legado.app.utils.applyTint
import io.legado.app.utils.cnCompare
import io.legado.app.utils.dpToPx
import io.legado.app.utils.gone
import io.legado.app.utils.observeEvent
import io.legado.app.utils.setLayout
import io.legado.app.utils.startActivity
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.visible
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.launch
@@ -99,7 +113,7 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
binding.toolBar.setBackgroundColor(primaryColor)
viewModel.initData(arguments)
viewModel.initData(arguments, callBack?.oldBook, activity is ReadBookActivity)
showTitle()
initMenu()
initView()
@@ -124,6 +138,8 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
?.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 initView() {
@@ -239,6 +255,11 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
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>()
else -> if (item?.groupId == R.id.source_group) {
@@ -15,8 +15,8 @@ class ChangeChapterSourceViewModel(application: Application) :
var chapterIndex: Int = 0
var chapterTitle: String = ""
override fun initData(arguments: Bundle?) {
super.initData(arguments)
override fun initData(arguments: Bundle?, book: Book?, fromReadBookActivity: Boolean) {
super.initData(arguments, book, fromReadBookActivity)
arguments?.let { bundle ->
bundle.getString("chapterTitle")?.let {
chapterTitle = it