pull/1751/head
kunfei 3 years ago
parent f666b0ec0c
commit ef59a1de90
  1. 43
      app/src/main/java/io/legado/app/data/entities/Book.kt
  2. 31
      app/src/main/java/io/legado/app/model/webBook/WebBook.kt
  3. 10
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookViewModel.kt
  4. 5
      app/src/main/java/io/legado/app/ui/book/audio/AudioPlayActivity.kt
  5. 56
      app/src/main/java/io/legado/app/ui/book/audio/AudioPlayViewModel.kt
  6. 30
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt
  7. 57
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt
  8. 35
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceDialog.kt
  9. 30
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceViewModel.kt
  10. 5
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  11. 66
      app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
  12. 4
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  13. 49
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  14. 2
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt

@ -6,6 +6,8 @@ import io.legado.app.constant.AppPattern
import io.legado.app.constant.BookType import io.legado.app.constant.BookType
import io.legado.app.constant.PageAnim import io.legado.app.constant.PageAnim
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.help.BookHelp
import io.legado.app.help.ContentProcessor
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig import io.legado.app.help.config.ReadBookConfig
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
@ -271,7 +273,13 @@ data class Book(
this.tocHtml = this@Book.tocHtml this.tocHtml = this@Book.tocHtml
} }
fun changeTo(newBook: Book) { fun changeTo(newBook: Book, toc: List<BookChapter>): Book {
newBook.durChapterIndex = BookHelp
.getDurChapter(durChapterIndex, durChapterTitle, toc, totalChapterNum)
newBook.durChapterTitle = toc[newBook.durChapterIndex].getDisplayTitle(
ContentProcessor.get(newBook.name, newBook.origin).getTitleReplaceRules()
)
newBook.durChapterPos = durChapterPos
newBook.group = group newBook.group = group
newBook.order = order newBook.order = order
newBook.customCoverUrl = customCoverUrl newBook.customCoverUrl = customCoverUrl
@ -279,23 +287,11 @@ data class Book(
newBook.customTag = customTag newBook.customTag = customTag
newBook.canUpdate = canUpdate newBook.canUpdate = canUpdate
newBook.readConfig = readConfig newBook.readConfig = readConfig
delete(this) if (appDb.bookDao.has(bookUrl) == true) {
delete()
appDb.bookDao.insert(newBook) appDb.bookDao.insert(newBook)
} }
return newBook
fun upInfoFromOld(oldBook: Book?) {
oldBook?.let {
group = oldBook.group
durChapterIndex = oldBook.durChapterIndex
durChapterPos = oldBook.durChapterPos
durChapterTitle = oldBook.durChapterTitle
customCoverUrl = oldBook.customCoverUrl
customIntro = oldBook.customIntro
order = oldBook.order
if (coverUrl.isNullOrEmpty()) {
coverUrl = oldBook.getDisplayCover()
}
}
} }
fun createBookMark(): Bookmark { fun createBookMark(): Bookmark {
@ -313,20 +309,19 @@ data class Book(
} }
} }
fun delete() {
if (ReadBook.book?.bookUrl == bookUrl) {
ReadBook.book = null
}
appDb.bookDao.delete(this)
}
companion object { companion object {
const val hTag = 2L const val hTag = 2L
const val rubyTag = 4L const val rubyTag = 4L
const val imgStyleDefault = "DEFAULT" const val imgStyleDefault = "DEFAULT"
const val imgStyleFull = "FULL" const val imgStyleFull = "FULL"
const val imgStyleText = "TEXT" const val imgStyleText = "TEXT"
fun delete(book: Book?) {
book ?: return
if (ReadBook.book?.bookUrl == book.bookUrl) {
ReadBook.book = null
}
appDb.bookDao.delete(book)
}
} }
@Parcelize @Parcelize

@ -320,35 +320,38 @@ object WebBook {
name: String, name: String,
author: String, author: String,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
): Coroutine<Pair<BookSource, Book>> { ): Coroutine<Pair<Book, BookSource>> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
preciseSearchAwait(scope, name, author, *bookSources.toTypedArray()) for (source in bookSources) {
?: throw NoStackTraceException("没有搜索到<$name>$author") val book = preciseSearchAwait(scope, source, name, author).getOrNull()
if (book != null) {
return@async Pair(book, source)
}
}
throw NoStackTraceException("没有搜索到<$name>$author")
} }
} }
suspend fun preciseSearchAwait( suspend fun preciseSearchAwait(
scope: CoroutineScope, scope: CoroutineScope,
bookSource: BookSource,
name: String, name: String,
author: String, author: String,
vararg bookSources: BookSource ): Result<Book?> {
): Pair<BookSource, Book>? { return kotlin.runCatching {
bookSources.forEach { source -> if (!scope.isActive) return@runCatching null
kotlin.runCatching { searchBookAwait(scope, bookSource, name).firstOrNull {
if (!scope.isActive) return null
searchBookAwait(scope, source, name).firstOrNull {
it.name == name && it.author == author it.name == name && it.author == author
}?.let { searchBook -> }?.let { searchBook ->
if (!scope.isActive) return null if (!scope.isActive) return@runCatching null
var book = searchBook.toBook() var book = searchBook.toBook()
if (book.tocUrl.isBlank()) { if (book.tocUrl.isBlank()) {
book = getBookInfoAwait(scope, source, book) book = getBookInfoAwait(scope, bookSource, book)
}
return Pair(source, book)
} }
return@runCatching book
} }
return@runCatching null
} }
return null
} }
} }

@ -46,14 +46,10 @@ class ArrangeBookViewModel(application: Application) : BaseViewModel(application
batchChangeSourcePosition.value = index + 1 batchChangeSourcePosition.value = index + 1
if (book.isLocalBook()) return@forEachIndexed if (book.isLocalBook()) return@forEachIndexed
if (book.origin == source.bookSourceUrl) return@forEachIndexed if (book.origin == source.bookSourceUrl) return@forEachIndexed
WebBook.preciseSearchAwait(this, book.name, book.author, source)?.let { WebBook.preciseSearchAwait(this, source, book.name, book.author)
val newBook = it.second .getOrNull()?.let { newBook ->
newBook.upInfoFromOld(book)
book.changeTo(newBook)
if (newBook.tocUrl.isEmpty()) {
WebBook.getBookInfoAwait(this, source, newBook)
}
val toc = WebBook.getChapterListAwait(this, source, newBook) val toc = WebBook.getChapterListAwait(this, source, newBook)
book.changeTo(newBook, toc)
appDb.bookChapterDao.insert(*toc.toTypedArray()) appDb.bookChapterDao.insert(*toc.toTypedArray())
} }
} }

@ -16,6 +16,7 @@ import io.legado.app.constant.EventBus
import io.legado.app.constant.Status import io.legado.app.constant.Status
import io.legado.app.constant.Theme import io.legado.app.constant.Theme
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.databinding.ActivityAudioPlayBinding import io.legado.app.databinding.ActivityAudioPlayBinding
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
@ -198,8 +199,8 @@ class AudioPlayActivity :
override val oldBook: Book? override val oldBook: Book?
get() = AudioPlay.book get() = AudioPlay.book
override fun changeTo(source: BookSource, book: Book) { override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
viewModel.changeTo(source, book) viewModel.changeTo(source, book, toc)
} }
override fun finish() { override fun finish() {

@ -9,8 +9,6 @@ import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.help.BookHelp
import io.legado.app.help.ContentProcessor
import io.legado.app.model.AudioPlay import io.legado.app.model.AudioPlay
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
@ -45,33 +43,23 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
} }
} }
private fun loadBookInfo( private fun loadBookInfo(book: Book) {
book: Book,
changeDruChapterIndex: ((chapters: List<BookChapter>) -> Unit)? = null
) {
execute { execute {
AudioPlay.bookSource?.let { AudioPlay.bookSource?.let {
WebBook.getBookInfo(this, it, book) WebBook.getBookInfo(this, it, book)
.onSuccess { .onSuccess {
loadChapterList(book, changeDruChapterIndex) loadChapterList(book)
} }
} }
} }
} }
private fun loadChapterList( private fun loadChapterList(book: Book) {
book: Book,
changeDruChapterIndex: ((chapters: List<BookChapter>) -> Unit)? = null
) {
execute { execute {
AudioPlay.bookSource?.let { AudioPlay.bookSource?.let {
WebBook.getChapterList(this, it, book) WebBook.getChapterList(this, it, book)
.onSuccess(Dispatchers.IO) { cList -> .onSuccess(Dispatchers.IO) { cList ->
if (changeDruChapterIndex == null) {
appDb.bookChapterDao.insert(*cList.toTypedArray()) appDb.bookChapterDao.insert(*cList.toTypedArray())
} else {
changeDruChapterIndex(cList)
}
AudioPlay.upDurChapter(book) AudioPlay.upDurChapter(book)
}.onError { }.onError {
context.toastOnUi(R.string.error_load_toc) context.toastOnUi(R.string.error_load_toc)
@ -88,47 +76,17 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun changeTo(source: BookSource, book: Book) { fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
execute { execute {
var oldTocSize: Int = book.totalChapterNum AudioPlay.book = AudioPlay.book!!.changeTo(book, toc)
AudioPlay.book?.let {
oldTocSize = it.totalChapterNum
book.order = it.order
appDb.bookDao.delete(it)
}
appDb.bookDao.insert(book)
AudioPlay.book = book
AudioPlay.bookSource = source AudioPlay.bookSource = source
if (book.tocUrl.isEmpty()) { appDb.bookChapterDao.insert(*toc.toTypedArray())
loadBookInfo(book) { upChangeDurChapterIndex(book, oldTocSize, it) } AudioPlay.upDurChapter(book)
} else {
loadChapterList(book) { upChangeDurChapterIndex(book, oldTocSize, it) }
}
}.onFinally { }.onFinally {
postEvent(EventBus.SOURCE_CHANGED, book.bookUrl) postEvent(EventBus.SOURCE_CHANGED, book.bookUrl)
} }
} }
private fun upChangeDurChapterIndex(
book: Book,
oldTocSize: Int,
chapters: List<BookChapter>
) {
execute {
book.durChapterIndex = BookHelp.getDurChapter(
book.durChapterIndex,
book.durChapterTitle,
chapters,
oldTocSize
)
book.durChapterTitle = chapters[book.durChapterIndex].getDisplayTitle(
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()
)
appDb.bookDao.update(book)
appDb.bookChapterDao.insert(*chapters.toTypedArray())
}
}
fun removeFromBookshelf(success: (() -> Unit)?) { fun removeFromBookshelf(success: (() -> Unit)?) {
execute { execute {
AudioPlay.book?.let { AudioPlay.book?.let {

@ -19,6 +19,7 @@ import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.databinding.DialogBookChangeSourceBinding import io.legado.app.databinding.DialogBookChangeSourceBinding
@ -27,6 +28,7 @@ import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.book.source.manage.BookSourceActivity
import io.legado.app.ui.widget.dialog.WaitDialog
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.* import io.legado.app.utils.*
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
@ -50,6 +52,7 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
private val groups = linkedSetOf<String>() private val groups = linkedSetOf<String>()
private val callBack: CallBack? get() = activity as? CallBack private val callBack: CallBack? get() = activity as? CallBack
private val viewModel: ChangeBookSourceViewModel by viewModels() private val viewModel: ChangeBookSourceViewModel by viewModels()
private val waitDialog by lazy { WaitDialog(requireContext()) }
private val adapter by lazy { ChangeBookSourceAdapter(requireContext(), viewModel, this) } private val adapter by lazy { ChangeBookSourceAdapter(requireContext(), viewModel, this) }
private val editSourceResult = private val editSourceResult =
registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) { registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) {
@ -241,9 +244,10 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
} }
override fun changeTo(searchBook: SearchBook) { override fun changeTo(searchBook: SearchBook) {
changeSource(searchBook) changeSource(searchBook) {
dismissAllowingStateLoss() dismissAllowingStateLoss()
} }
}
override val bookUrl: String? override val bookUrl: String?
get() = callBack?.oldBook?.bookUrl get() = callBack?.oldBook?.bookUrl
@ -269,22 +273,22 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
override fun deleteSource(searchBook: SearchBook) { override fun deleteSource(searchBook: SearchBook) {
viewModel.del(searchBook) viewModel.del(searchBook)
if (bookUrl == searchBook.bookUrl) { if (bookUrl == searchBook.bookUrl) {
viewModel.firstSourceOrNull(searchBook)?.let { viewModel.autoChangeSource { book, toc, source ->
changeSource(it) callBack?.changeTo(source, book, toc)
} }
} }
} }
private fun changeSource(searchBook: SearchBook) { private fun changeSource(searchBook: SearchBook, onSuccess: (() -> Unit)? = null) {
try { waitDialog.setText(R.string.load_toc)
waitDialog.show()
val book = searchBook.toBook() val book = searchBook.toBook()
book.upInfoFromOld(callBack?.oldBook) viewModel.getToc(book, {
val source = appDb.bookSourceDao.getBookSource(book.origin) waitDialog.dismiss()
callBack?.changeTo(source!!, book) toastOnUi(it)
searchBook.time = System.currentTimeMillis() }) { toc, source ->
viewModel.updateSource(searchBook) callBack?.changeTo(source, book, toc)
} catch (e: Exception) { onSuccess?.invoke()
toastOnUi("换源失败\n${e.localizedMessage}")
} }
} }
@ -325,7 +329,7 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
interface CallBack { interface CallBack {
val oldBook: Book? val oldBook: Book?
fun changeTo(source: BookSource, book: Book) fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>)
} }
} }

@ -11,14 +11,17 @@ import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ExecutorCoroutineDispatcher import kotlinx.coroutines.ExecutorCoroutineDispatcher
@ -27,6 +30,7 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import splitties.init.appCtx import splitties.init.appCtx
import java.util.* import java.util.*
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -86,6 +90,11 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
@Volatile @Volatile
private var searchIndex = -1 private var searchIndex = -1
override fun onCleared() {
super.onCleared()
searchPool?.close()
}
@CallSuper @CallSuper
open fun initData(arguments: Bundle?) { open fun initData(arguments: Bundle?) {
arguments?.let { bundle -> arguments?.let { bundle ->
@ -253,9 +262,32 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
searchStateData.postValue(false) searchStateData.postValue(false)
} }
override fun onCleared() { open fun getToc(
super.onCleared() book: Book,
searchPool?.close() onError: (msg: String) -> Unit,
onSuccess: (toc: List<BookChapter>, source: BookSource) -> Unit
) {
execute {
getToc(book).getOrThrow()
}.onError {
onError.invoke(it.localizedMessage ?: "加载目录错误")
}.onSuccess {
onSuccess.invoke(it.first, it.second)
}
}
suspend fun getToc(book: Book): Result<Pair<List<BookChapter>, BookSource>> {
return kotlin.runCatching {
withContext(IO) {
val source = appDb.bookSourceDao.getBookSource(book.origin)
?: throw NoStackTraceException("书源不存在")
if (book.tocUrl.isEmpty()) {
WebBook.getBookInfoAwait(this, source, book)
}
val toc = WebBook.getChapterListAwait(this, source, book)
Pair(toc, source)
}
}
} }
fun disableSource(searchBook: SearchBook) { fun disableSource(searchBook: SearchBook) {
@ -310,8 +342,23 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
searchCallback?.upAdapter() searchCallback?.upAdapter()
} }
fun firstSourceOrNull(searchBook: SearchBook): SearchBook? { fun autoChangeSource(
return searchBooks.firstOrNull { it.bookUrl != searchBook.bookUrl } onSuccess: (book: Book, toc: List<BookChapter>, source: BookSource) -> Unit
) {
execute {
searchBooks.forEach {
val book = it.toBook()
val result = getToc(book).getOrNull()
if (result != null) {
return@execute Triple(book, result.first, result.second)
}
}
throw NoStackTraceException("没有有效源")
}.onSuccess {
onSuccess.invoke(it.first, it.second, it.third)
}.onError {
context.toastOnUi("自动换源失败\n${it.localizedMessage}")
}
} }
interface SourceCallback { interface SourceCallback {

@ -67,13 +67,6 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
private val tocAdapter by lazy { private val tocAdapter by lazy {
ChangeChapterTocAdapter(requireContext(), this) ChangeChapterTocAdapter(requireContext(), this)
} }
private val tocSuccess: (toc: List<BookChapter>) -> Unit = {
tocAdapter.durChapterIndex =
BookHelp.getDurChapter(viewModel.chapterIndex, viewModel.chapterTitle, it)
binding.loadingToc.hide()
tocAdapter.setItems(it)
binding.recyclerViewToc.scrollToPosition(tocAdapter.durChapterIndex - 5)
}
private val contentSuccess: (content: String) -> Unit = { private val contentSuccess: (content: String) -> Unit = {
binding.loadingToc.hide() binding.loadingToc.hide()
callBack?.replaceContent(it) callBack?.replaceContent(it)
@ -279,9 +272,16 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
tocAdapter.setItems(null) tocAdapter.setItems(null)
binding.clToc.visible() binding.clToc.visible()
binding.loadingToc.show() binding.loadingToc.show()
viewModel.getToc(searchBook, tocSuccess) { val book = searchBook.toBook()
viewModel.getToc(book, {
binding.clToc.gone() binding.clToc.gone()
toastOnUi(it) toastOnUi(it)
}) { toc: List<BookChapter>, _: BookSource ->
tocAdapter.durChapterIndex =
BookHelp.getDurChapter(viewModel.chapterIndex, viewModel.chapterTitle, toc)
binding.loadingToc.hide()
tocAdapter.setItems(toc)
binding.recyclerViewToc.scrollToPosition(tocAdapter.durChapterIndex - 5)
} }
} }
@ -309,8 +309,8 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
override fun deleteSource(searchBook: SearchBook) { override fun deleteSource(searchBook: SearchBook) {
viewModel.del(searchBook) viewModel.del(searchBook)
if (bookUrl == searchBook.bookUrl) { if (bookUrl == searchBook.bookUrl) {
viewModel.firstSourceOrNull(searchBook)?.let { viewModel.autoChangeSource { book, toc, source ->
changeSource(it) callBack?.changeTo(source, book, toc)
} }
} }
} }
@ -326,19 +326,6 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
} }
} }
private fun changeSource(searchBook: SearchBook) {
try {
val book = searchBook.toBook()
book.upInfoFromOld(callBack?.oldBook)
val source = appDb.bookSourceDao.getBookSource(book.origin)
callBack?.changeTo(source!!, book)
searchBook.time = System.currentTimeMillis()
viewModel.updateSource(searchBook)
} catch (e: Exception) {
toastOnUi("换源失败\n${e.localizedMessage}")
}
}
/** /**
* 更新分组菜单 * 更新分组菜单
*/ */
@ -388,7 +375,7 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
interface CallBack { interface CallBack {
val oldBook: Book? val oldBook: Book?
fun changeTo(source: BookSource, book: Book) fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>)
fun replaceContent(content: String) fun replaceContent(content: String)
} }

@ -5,7 +5,7 @@ import android.os.Bundle
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.BookSource
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -29,28 +29,24 @@ class ChangeChapterSourceViewModel(application: Application) :
} }
} }
fun getToc( override fun getToc(
searchBook: SearchBook, book: Book,
success: (toc: List<BookChapter>) -> Unit, onError: (msg: String) -> Unit,
error: (msg: String) -> Unit onSuccess: (toc: List<BookChapter>, source: BookSource) -> Unit
) { ) {
execute { execute {
return@execute tocMap[searchBook.bookUrl] val toc = tocMap[book.bookUrl]
?: let { if (toc != null) {
val book = searchBook.toBook()
val source = appDb.bookSourceDao.getBookSource(book.origin) val source = appDb.bookSourceDao.getBookSource(book.origin)
?: throw NoStackTraceException("书源不存在") return@execute Pair(toc, source!!)
if (book.tocUrl.isEmpty()) {
WebBook.getBookInfoAwait(this, source, book)
}
val toc = WebBook.getChapterListAwait(this, source, book)
tocMap[book.bookUrl] = toc
toc
} }
val result = getToc(book).getOrThrow()
tocMap[book.bookUrl] = result.first
return@execute result
}.onSuccess { }.onSuccess {
success(it) onSuccess.invoke(it.first, it.second)
}.onError { }.onError {
error(it.localizedMessage ?: "获取目录出错") onError.invoke(it.localizedMessage ?: "获取目录出错")
} }
} }

@ -457,9 +457,8 @@ class BookInfoActivity :
override val oldBook: Book? override val oldBook: Book?
get() = viewModel.bookData.value get() = viewModel.bookData.value
override fun changeTo(source: BookSource, book: Book) { override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
upLoading(true) viewModel.changeTo(source, book, toc)
viewModel.changeTo(source, book)
} }
override fun coverChangeTo(coverUrl: String) { override fun coverChangeTo(coverUrl: String) {

@ -14,7 +14,6 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.ContentProcessor
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.BookCover import io.legado.app.model.BookCover
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
@ -24,7 +23,6 @@ import io.legado.app.utils.postEvent
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ensureActive
class BookInfoViewModel(application: Application) : BaseViewModel(application) { class BookInfoViewModel(application: Application) : BaseViewModel(application) {
val bookData = MutableLiveData<Book>() val bookData = MutableLiveData<Book>()
@ -107,12 +105,11 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
fun loadBookInfo( fun loadBookInfo(
book: Book, book: Book,
canReName: Boolean = true, canReName: Boolean = true,
scope: CoroutineScope = viewModelScope, scope: CoroutineScope = viewModelScope
changeDruChapterIndex: ((chapters: List<BookChapter>) -> Unit)? = null,
) { ) {
execute(scope) { execute(scope) {
if (book.isLocalBook()) { if (book.isLocalBook()) {
loadChapter(book, scope, changeDruChapterIndex) loadChapter(book, scope)
} else { } else {
bookSource?.let { bookSource -> bookSource?.let { bookSource ->
WebBook.getBookInfo(this, bookSource, book, canReName = canReName) WebBook.getBookInfo(this, bookSource, book, canReName = canReName)
@ -121,7 +118,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
if (inBookshelf) { if (inBookshelf) {
appDb.bookDao.update(book) appDb.bookDao.update(book)
} }
loadChapter(it, scope, changeDruChapterIndex) loadChapter(it, scope)
}.onError { }.onError {
AppLog.put("获取数据信息失败\n${it.localizedMessage}", it) AppLog.put("获取数据信息失败\n${it.localizedMessage}", it)
context.toastOnUi(R.string.error_get_book_info) context.toastOnUi(R.string.error_get_book_info)
@ -136,8 +133,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
private fun loadChapter( private fun loadChapter(
book: Book, book: Book,
scope: CoroutineScope = viewModelScope, scope: CoroutineScope = viewModelScope
changeDruChapterIndex: ((chapters: List<BookChapter>) -> Unit)? = null,
) { ) {
execute(scope) { execute(scope) {
if (book.isLocalBook()) { if (book.isLocalBook()) {
@ -156,11 +152,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
appDb.bookChapterDao.delByBook(book.bookUrl) appDb.bookChapterDao.delByBook(book.bookUrl)
appDb.bookChapterDao.insert(*it.toTypedArray()) appDb.bookChapterDao.insert(*it.toTypedArray())
} }
if (changeDruChapterIndex == null) {
chapterListData.postValue(it) chapterListData.postValue(it)
} else {
changeDruChapterIndex(it)
}
}.onError { }.onError {
chapterListData.postValue(emptyList()) chapterListData.postValue(emptyList())
AppLog.put("获取目录失败\n${it.localizedMessage}", it) AppLog.put("获取目录失败\n${it.localizedMessage}", it)
@ -184,58 +176,18 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
} }
} }
fun changeTo(source: BookSource, newBook: Book) { fun changeTo(source: BookSource, newBook: Book, toc: List<BookChapter>) {
changeSourceCoroutine?.cancel() changeSourceCoroutine?.cancel()
changeSourceCoroutine = execute { changeSourceCoroutine = execute {
var oldTocSize: Int = newBook.totalChapterNum
if (inBookshelf) {
bookData.value?.let {
oldTocSize = it.totalChapterNum
it.changeTo(newBook)
}
}
bookData.postValue(newBook)
bookSource = source bookSource = source
if (newBook.tocUrl.isEmpty()) { bookData.value!!.changeTo(newBook, toc)
loadBookInfo(newBook, false, this) { bookData.postValue(newBook)
ensureActive() chapterListData.postValue(toc)
upChangeDurChapterIndex(newBook, oldTocSize, it)
}
} else {
loadChapter(newBook, this) {
ensureActive()
upChangeDurChapterIndex(newBook, oldTocSize, it)
}
}
}.onFinally { }.onFinally {
postEvent(EventBus.SOURCE_CHANGED, newBook.bookUrl) postEvent(EventBus.SOURCE_CHANGED, newBook.bookUrl)
} }
} }
private fun upChangeDurChapterIndex(
book: Book,
oldTocSize: Int,
chapters: List<BookChapter>
) {
execute {
book.durChapterIndex = BookHelp.getDurChapter(
book.durChapterIndex,
book.durChapterTitle,
chapters,
oldTocSize
)
book.durChapterTitle = chapters[book.durChapterIndex].getDisplayTitle(
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()
)
if (inBookshelf) {
appDb.bookDao.update(book)
appDb.bookChapterDao.insert(*chapters.toTypedArray())
}
bookData.postValue(book)
chapterListData.postValue(chapters)
}
}
fun topBook() { fun topBook() {
execute { execute {
bookData.value?.let { book -> bookData.value?.let { book ->
@ -300,7 +252,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
fun delBook(deleteOriginal: Boolean = false, success: (() -> Unit)? = null) { fun delBook(deleteOriginal: Boolean = false, success: (() -> Unit)? = null) {
execute { execute {
bookData.value?.let { bookData.value?.let {
Book.delete(it) it.delete()
inBookshelf = false inBookshelf = false
if (it.isLocalBook()) { if (it.isLocalBook()) {
LocalBook.deleteBook(it, deleteOriginal) LocalBook.deleteBook(it, deleteOriginal)

@ -715,8 +715,8 @@ class ReadBookActivity : BaseReadBookActivity(),
override val oldBook: Book? override val oldBook: Book?
get() = ReadBook.book get() = ReadBook.book
override fun changeTo(source: BookSource, book: Book) { override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
viewModel.changeTo(source, book) viewModel.changeTo(source, book, toc)
} }
override fun replaceContent(content: String) { override fun replaceContent(content: String) {

@ -10,6 +10,7 @@ import io.legado.app.constant.AppLog
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookProgress import io.legado.app.data.entities.BookProgress
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
@ -30,7 +31,6 @@ import io.legado.app.utils.postEvent
import io.legado.app.utils.toStringArray import io.legado.app.utils.toStringArray
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ensureActive
class ReadBookViewModel(application: Application) : BaseViewModel(application) { class ReadBookViewModel(application: Application) : BaseViewModel(application) {
val permissionDenialLiveData = MutableLiveData<Int>() val permissionDenialLiveData = MutableLiveData<Int>()
@ -191,40 +191,22 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
/** /**
* 换源 * 换源
*/ */
fun changeTo(source: BookSource, book: Book) { fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
changeSourceCoroutine?.cancel() changeSourceCoroutine?.cancel()
changeSourceCoroutine = execute { changeSourceCoroutine = execute {
ReadBook.upMsg(context.getString(R.string.loading)) ReadBook.upMsg(context.getString(R.string.loading))
if (book.tocUrl.isEmpty()) { ReadBook.book!!.changeTo(book, toc)
WebBook.getBookInfoAwait(this, source, book) val nextChapter = toc.getOrElse(book.durChapterIndex) {
} toc.first()
ensureActive()
val chapters = WebBook.getChapterListAwait(this, source, book)
ensureActive()
val oldBook = ReadBook.book!!
book.durChapterIndex = BookHelp.getDurChapter(
oldBook.durChapterIndex,
oldBook.durChapterTitle,
chapters,
oldBook.totalChapterNum
)
book.durChapterTitle = chapters[book.durChapterIndex].getDisplayTitle(
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()
)
ensureActive()
val nextChapter = chapters.getOrElse(book.durChapterIndex) {
chapters.first()
} }
WebBook.getContentAwait( WebBook.getContentAwait(
this, this,
bookSource = source, bookSource = source,
book = book, book = book,
bookChapter = chapters[book.durChapterIndex], bookChapter = toc[book.durChapterIndex],
nextChapterUrl = nextChapter.url nextChapterUrl = nextChapter.url
) )
ensureActive() appDb.bookChapterDao.insert(*toc.toTypedArray())
oldBook.changeTo(book)
appDb.bookChapterDao.insert(*chapters.toTypedArray())
ReadBook.resetData(book) ReadBook.resetData(book)
ReadBook.upMsg(null) ReadBook.upMsg(null)
ReadBook.loadContent(resetPageOffset = true) ReadBook.loadContent(resetPageOffset = true)
@ -244,10 +226,17 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
if (!AppConfig.autoChangeSource) return if (!AppConfig.autoChangeSource) return
execute { execute {
val sources = appDb.bookSourceDao.allTextEnabled val sources = appDb.bookSourceDao.allTextEnabled
WebBook.preciseSearchAwait(this, name, author, *sources.toTypedArray())?.let { sources.forEach { source ->
it.second.upInfoFromOld(ReadBook.book) WebBook.preciseSearchAwait(this, source, name, author).getOrNull()?.let { book ->
changeTo(it.first, it.second) if (book.tocUrl.isEmpty()) {
} ?: throw NoStackTraceException("自动换源失败") WebBook.getBookInfoAwait(this, source, book)
}
val toc = WebBook.getChapterListAwait(this, source, book)
changeTo(source, book, toc)
return@execute
}
}
throw NoStackTraceException("自动换源失败")
}.onStart { }.onStart {
ReadBook.upMsg(context.getString(R.string.source_auto_changing)) ReadBook.upMsg(context.getString(R.string.source_auto_changing))
}.onError { }.onError {
@ -272,7 +261,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
fun removeFromBookshelf(success: (() -> Unit)?) { fun removeFromBookshelf(success: (() -> Unit)?) {
execute { execute {
Book.delete(ReadBook.book) ReadBook.book?.delete()
}.onSuccess { }.onSuccess {
success?.invoke() success?.invoke()
} }

@ -132,7 +132,7 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
if (name.isNotEmpty() && appDb.bookDao.getBook(name, author) == null) { if (name.isNotEmpty() && appDb.bookDao.getBook(name, author) == null) {
WebBook.preciseSearch(this, bookSources, name, author) WebBook.preciseSearch(this, bookSources, name, author)
.onSuccess { .onSuccess {
val book = it.second val book = it.first
if (groupId > 0) { if (groupId > 0) {
book.group = groupId book.group = groupId
} }

Loading…
Cancel
Save