允许书籍共存

This commit is contained in:
HapeLee
2025-10-18 01:31:04 +08:00
parent c673169456
commit f92f386e08
17 changed files with 1990 additions and 44 deletions
@@ -65,7 +65,7 @@ val appDb by lazy {
}
@Database(
version = 76,
version = 77,
exportSchema = true,
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
@@ -106,7 +106,8 @@ val appDb by lazy {
AutoMigration(from = 72, to = 73),
AutoMigration(from = 73, to = 74),
AutoMigration(from = 74, to = 75),
AutoMigration(from = 75, to = 76)
AutoMigration(from = 75, to = 76),
AutoMigration(from = 76, to = 77)
]
)
abstract class AppDatabase : RoomDatabase() {
@@ -33,7 +33,7 @@ import kotlin.math.max
@TypeConverters(Book.Converters::class)
@Entity(
tableName = "books",
indices = [Index(value = ["name", "author"], unique = true)]
indices = [Index(value = ["name", "author"], unique = false)]
)
data class Book(
// 详情页Url(本地书源存储完整文件路径)
@@ -61,6 +61,7 @@ import io.legato.kazusa.utils.showDialogFragment
import io.legato.kazusa.utils.startActivity
import io.legato.kazusa.utils.startActivityForBook
import io.legato.kazusa.utils.startAnimation
import io.legato.kazusa.utils.toastOnUi
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
import io.legato.kazusa.utils.visible
import kotlinx.coroutines.CoroutineScope
@@ -688,6 +689,12 @@ class AudioPlayActivity :
}
}
override fun addToBookshelf(book: Book, toc: List<BookChapter>) {
viewModel.addToBookshelf(book, toc) {
toastOnUi("已添加到书架")
}
}
}
private fun Int.copy(alpha: Float): Int {
@@ -117,4 +117,21 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
}
}
fun addToBookshelf(book: Book, toc: List<BookChapter>, success: (() -> Unit)? = null) {
execute {
book.removeType(BookType.notShelf)
if (book.order == 0) {
book.order = appDb.bookDao.minOrder - 1
}
appDb.bookDao.insert(book)
appDb.bookChapterDao.insert(*toc.toTypedArray())
}.onSuccess {
success?.invoke()
}.onError {
AppLog.put("添加书籍到书架失败", it)
context.toastOnUi("添加书籍失败")
}
}
}
@@ -20,6 +20,7 @@ import androidx.recyclerview.widget.RecyclerView
import io.legato.kazusa.R
import io.legato.kazusa.base.BaseBottomSheetDialogFragment
import io.legato.kazusa.constant.AppLog
import io.legato.kazusa.constant.BookType
import io.legato.kazusa.constant.EventBus
import io.legato.kazusa.data.appDb
import io.legato.kazusa.data.entities.Book
@@ -29,6 +30,7 @@ import io.legato.kazusa.data.entities.SearchBook
import io.legato.kazusa.databinding.DialogBookChangeSourceBinding
import io.legato.kazusa.help.config.AppConfig
import io.legato.kazusa.lib.dialogs.alert
import io.legato.kazusa.model.ReadBook
import io.legato.kazusa.ui.book.read.ReadBookActivity
import io.legato.kazusa.ui.book.source.edit.BookSourceEditActivity
import io.legato.kazusa.ui.book.source.manage.BookSourceActivity
@@ -40,6 +42,7 @@ import io.legato.kazusa.utils.dpToPx
import io.legato.kazusa.utils.getCompatDrawable
import io.legato.kazusa.utils.observeEvent
import io.legato.kazusa.utils.startActivity
import io.legato.kazusa.utils.toastOnUi
import io.legato.kazusa.utils.transaction
import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
import io.legato.kazusa.utils.visible
@@ -74,6 +77,7 @@ class ChangeBookSourceDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_b
val origin = it.data?.getStringExtra("origin") ?: return@registerForActivityResult
viewModel.startSearch(origin)
}
private var currentSelectedSearchBook: SearchBook? = null
private val searchFinishCallback: (isEmpty: Boolean) -> Unit = {
if (it) {
val searchGroup = AppConfig.searchGroup
@@ -327,23 +331,65 @@ class ChangeBookSourceDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_b
}
override fun changeTo(searchBook: SearchBook) {
currentSelectedSearchBook = searchBook
showChangeSourceOptionDialog(searchBook)
}
private fun showChangeSourceOptionDialog(searchBook: SearchBook) {
val oldBookType = callBack?.oldBook?.type ?: 0
if (searchBook.sameBookTypeLocal(oldBookType)) {
changeSource(searchBook) {
dismissAllowingStateLoss()
}
} else {
if (!searchBook.sameBookTypeLocal(oldBookType)) {
alert(
titleResource = R.string.book_type_different,
messageResource = R.string.soure_change_source
) {
okButton {
changeSource(searchBook) {
dismissAllowingStateLoss()
}
showChangeSourceActionDialog(searchBook)
}
cancelButton()
}
} else {
showChangeSourceActionDialog(searchBook)
}
}
private fun showChangeSourceActionDialog(searchBook: SearchBook) {
context?.alert(getString(R.string.change_source_option_title)) {
positiveButton(getString(R.string.replace_current_book)) {
performChangeSource(searchBook, true)
}
negativeButton(getString(R.string.add_as_new_book)) {
performChangeSource(searchBook, false)
}
}
}
private fun performChangeSource(searchBook: SearchBook, isReplace: Boolean) {
val baseBook = viewModel.bookMap[searchBook.primaryStr()] ?: searchBook.toBook()
val book = baseBook
waitDialog.setText(if (isReplace) R.string.load_toc else R.string.load_info)
waitDialog.show()
val coroutine = viewModel.getToc(book, { toc, source ->
waitDialog.dismiss()
if (isReplace) {
callBack?.changeTo(source, book, toc)
dismissAllowingStateLoss()
} else {
ReadBook.book?.migrateTo(book, toc)
callBack?.addToBookshelf(book, toc)
context?.toastOnUi(getString(R.string.book_added_to_shelf))
}
}, {
waitDialog.dismiss()
AppLog.put("${if (isReplace) "换源" else "添加书籍"}获取目录出错\n$it", it, true)
context?.toastOnUi("${if (isReplace) "换源" else "添加书籍"}失败")
})
waitDialog.setOnCancelListener {
coroutine.cancel()
}
}
@@ -457,6 +503,7 @@ class ChangeBookSourceDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_b
interface CallBack {
val oldBook: Book?
fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>)
fun addToBookshelf(book: Book, toc: List<BookChapter>)
}
}
@@ -1060,4 +1060,9 @@ class BookInfoActivity :
}
}
override fun addToBookshelf(book: Book, toc: List<BookChapter>) {
viewModel.addToBookshelf(book, toc) {
toastOnUi("已添加到书架")
}
}
}
@@ -15,6 +15,7 @@ import io.legato.kazusa.data.appDb
import io.legato.kazusa.data.entities.Book
import io.legato.kazusa.data.entities.BookChapter
import io.legato.kazusa.data.entities.BookSource
import io.legato.kazusa.data.entities.SearchBook
import io.legato.kazusa.exception.NoBooksDirException
import io.legato.kazusa.exception.NoStackTraceException
import io.legato.kazusa.help.AppWebDav
@@ -56,28 +57,17 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
fun initData(intent: Intent) {
execute {
val name = intent.getStringExtra("name") ?: ""
val author = intent.getStringExtra("author") ?: ""
val bookUrl = intent.getStringExtra("bookUrl") ?: ""
appDb.bookDao.getBook(name, author)?.let {
appDb.bookDao.getBook(bookUrl)?.let {
inBookshelf = !it.isNotShelf
upBook(it)
return@execute
}
if (bookUrl.isNotBlank()) {
appDb.searchBookDao.getSearchBook(bookUrl)?.toBook()?.let {
upBook(it)
return@execute
}
}
appDb.searchBookDao.getFirstByNameAuthor(name, author)?.toBook()?.let {
appDb.searchBookDao.getSearchBook(bookUrl)?.toBook()?.let {
upBook(it)
return@execute
}
throw NoStackTraceException("未找到书籍")
}.onError {
AppLog.put(it.localizedMessage, it)
context.toastOnUi(it.localizedMessage)
}
}
@@ -85,9 +75,12 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
execute {
val name = intent.getStringExtra("name") ?: ""
val author = intent.getStringExtra("author") ?: ""
appDb.bookDao.getBook(name, author)?.let { book ->
upBook(book)
val bookUrl = intent.getStringExtra("bookUrl") ?: ""
val book = when {
bookUrl.isNotBlank() -> appDb.bookDao.getBook(bookUrl)
else -> appDb.bookDao.getBook(name, author)
}
book?.let { upBook(it) }
}
}
@@ -484,6 +477,24 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
}
}
fun addToBookshelf(book: Book, toc: List<BookChapter>, success: (() -> Unit)? = null) {
execute {
book.removeType(BookType.notShelf)
if (book.order == 0) {
book.order = appDb.bookDao.minOrder - 1
}
appDb.bookDao.insert(book)
appDb.bookChapterDao.insert(*toc.toTypedArray())
}.onSuccess {
success?.invoke()
}.onError {
AppLog.put("添加书籍到书架失败", it)
context.toastOnUi("添加书籍失败")
}
}
fun upEditBook() {
bookData.value?.let {
appDb.bookDao.getBook(it.bookUrl)?.let { book ->
@@ -685,6 +685,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
bookInfoActivity.launch {
putExtra("name", it.name)
putExtra("author", it.author)
putExtra("bookUrl", it.bookUrl)
}
}
}
@@ -956,5 +957,10 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
return super.onKeyDown(keyCode, event)
}
override fun addToBookshelf(book: Book, toc: List<BookChapter>) {
viewModel.addToBookshelf(book, toc) {
toastOnUi("已添加到书架")
}
}
}
@@ -352,4 +352,21 @@ class ReadMangaViewModel(application: Application) : BaseViewModel(application)
}
}
}
fun addToBookshelf(book: Book, toc: List<BookChapter>, success: (() -> Unit)? = null) {
execute {
book.removeType(BookType.notShelf)
if (book.order == 0) {
book.order = appDb.bookDao.minOrder - 1
}
appDb.bookDao.insert(book)
appDb.bookChapterDao.insert(*toc.toTypedArray())
}.onSuccess {
success?.invoke()
}.onError {
AppLog.put("添加书籍到书架失败", it)
context.toastOnUi("添加书籍失败")
}
}
}
@@ -60,6 +60,7 @@ import io.legato.kazusa.lib.dialogs.alert
import io.legato.kazusa.lib.dialogs.selector
import io.legato.kazusa.model.ReadAloud
import io.legato.kazusa.model.ReadBook
import io.legato.kazusa.model.ReadManga
import io.legato.kazusa.model.analyzeRule.AnalyzeRule
import io.legato.kazusa.model.analyzeRule.AnalyzeRule.Companion.setChapter
import io.legato.kazusa.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext
@@ -202,7 +203,7 @@ class ReadBookActivity : BaseReadBookActivity(),
registerForActivityResult(StartActivityContract(BookInfoActivity::class.java)) {
if (it.resultCode == RESULT_OK) {
setResult(RESULT_DELETED)
super.finish()
finish()
} else {
ReadBook.loadOrUpContent()
}
@@ -1206,10 +1207,11 @@ class ReadBookActivity : BaseReadBookActivity(),
}
override fun openBookInfoActivity() {
ReadBook.book?.let {
ReadManga.book?.let {
bookInfoActivity.launch {
putExtra("name", it.name)
putExtra("author", it.author)
putExtra("bookUrl", it.bookUrl)
}
}
}
@@ -1685,6 +1687,7 @@ class ReadBookActivity : BaseReadBookActivity(),
ReadBook.book?.save()
ReadBook.inBookshelf = true
setResult(RESULT_OK)
super.finish()
}
noButton { viewModel.removeFromBookshelf { super.finish() } }
}
@@ -1803,6 +1806,12 @@ class ReadBookActivity : BaseReadBookActivity(),
}
}
override fun addToBookshelf(book: Book, toc: List<BookChapter>) {
viewModel.addToBookshelf(book, toc) {
toastOnUi("已添加到书架")
}
}
companion object {
const val RESULT_DELETED = 100
}
@@ -545,4 +545,21 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
}
}
fun addToBookshelf(book: Book, toc: List<BookChapter>, success: (() -> Unit)? = null) {
execute {
book.removeType(BookType.notShelf)
if (book.order == 0) {
book.order = appDb.bookDao.minOrder - 1
}
appDb.bookDao.insert(book)
appDb.bookChapterDao.insert(*toc.toTypedArray())
}.onSuccess {
success?.invoke()
}.onError {
AppLog.put("添加书籍到书架失败", it)
context.toastOnUi("添加书籍失败")
}
}
}
@@ -450,7 +450,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
* 搜索结束
*/
private fun searchFinally() {
binding.refreshProgressBar.gone()
binding.refreshProgressBar.invisible()
binding.cdProgress.gone()
if (!isManualStopSearch && viewModel.hasMore) {
binding.fbStartStop.setImageResource(R.drawable.ic_play)
@@ -300,6 +300,7 @@ class BooksFragment() : BaseFragment(R.layout.fragment_books),
val intent = Intent(requireContext(), BookInfoActivity::class.java).apply {
putExtra("name", book.name)
putExtra("author", book.author)
putExtra("bookUrl", book.bookUrl)
putExtra("transitionName", "book_${book.bookUrl}") // 给共享元素唯一标识
}