AddToBookshelfDialog.kt 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package io.legato.kazusa.ui.association
  2. import android.annotation.SuppressLint
  3. import android.app.Application
  4. import android.content.DialogInterface
  5. import android.os.Bundle
  6. import android.view.View
  7. import android.view.ViewGroup
  8. import androidx.core.view.isVisible
  9. import androidx.fragment.app.viewModels
  10. import androidx.lifecycle.MutableLiveData
  11. import io.legato.kazusa.R
  12. import io.legato.kazusa.base.BaseDialogFragment
  13. import io.legato.kazusa.base.BaseViewModel
  14. import io.legato.kazusa.constant.AppLog
  15. import io.legato.kazusa.data.appDb
  16. import io.legato.kazusa.data.entities.Book
  17. import io.legato.kazusa.data.entities.BookSource
  18. import io.legato.kazusa.databinding.DialogAddToBookshelfBinding
  19. import io.legato.kazusa.exception.NoStackTraceException
  20. import io.legato.kazusa.model.analyzeRule.AnalyzeUrl
  21. import io.legato.kazusa.model.webBook.WebBook
  22. import io.legato.kazusa.ui.book.info.BookInfoActivity
  23. import io.legato.kazusa.utils.GSON
  24. import io.legato.kazusa.utils.NetworkUtils
  25. import io.legato.kazusa.utils.fromJsonObject
  26. import io.legato.kazusa.utils.setLayout
  27. import io.legato.kazusa.utils.startActivity
  28. import io.legato.kazusa.utils.toastOnUi
  29. import io.legato.kazusa.utils.viewbindingdelegate.viewBinding
  30. /**
  31. * 添加书籍链接到书架,需要对应网站书源
  32. * ${origin}/${path}, {origin: bookSourceUrl}
  33. * 按以下顺序尝试匹配书源并添加网址
  34. * - UrlOption中的指定的书源网址bookSourceUrl
  35. * - 在所有启用的书源中匹配orgin
  36. * - 在所有启用的书源中使用详情页正则匹配${origin}/${path}, {origin: bookSourceUrl}
  37. */
  38. class AddToBookshelfDialog() : BaseDialogFragment(R.layout.dialog_add_to_bookshelf) {
  39. constructor(bookUrl: String, finishOnDismiss: Boolean = false) : this() {
  40. arguments = Bundle().apply {
  41. putString("bookUrl", bookUrl)
  42. putBoolean("finishOnDismiss", finishOnDismiss)
  43. }
  44. }
  45. val binding by viewBinding(DialogAddToBookshelfBinding::bind)
  46. val viewModel by viewModels<ViewModel>()
  47. override fun onStart() {
  48. super.onStart()
  49. setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)
  50. }
  51. override fun onDismiss(dialog: DialogInterface) {
  52. super.onDismiss(dialog)
  53. if (arguments?.getBoolean("finishOnDismiss") == true) {
  54. activity?.finish()
  55. }
  56. }
  57. @SuppressLint("SetTextI18n")
  58. override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
  59. val bookUrl = arguments?.getString("bookUrl")
  60. if (bookUrl.isNullOrBlank()) {
  61. toastOnUi("url不能为空")
  62. dismiss()
  63. return
  64. }
  65. viewModel.loadStateLiveData.observe(this) {
  66. if (it) {
  67. binding.rotateLoading.isVisible = true
  68. } else {
  69. binding.rotateLoading.isVisible = true
  70. }
  71. }
  72. viewModel.loadErrorLiveData.observe(this) {
  73. toastOnUi(it)
  74. dismiss()
  75. }
  76. viewModel.load(bookUrl) {
  77. viewModel.saveSearchBook(it) {
  78. startActivity<BookInfoActivity> {
  79. putExtra("name", it.name)
  80. putExtra("author", it.author)
  81. putExtra("bookUrl", it.bookUrl)
  82. }
  83. dismiss()
  84. }
  85. }
  86. binding.tvCancel.setOnClickListener {
  87. dismiss()
  88. }
  89. }
  90. class ViewModel(application: Application) : BaseViewModel(application) {
  91. val loadStateLiveData = MutableLiveData<Boolean>()
  92. val loadErrorLiveData = MutableLiveData<String>()
  93. var book: Book? = null
  94. fun load(bookUrl: String, success: (book: Book) -> Unit) {
  95. execute {
  96. appDb.bookDao.getBook(bookUrl)?.let {
  97. throw NoStackTraceException("${it.name} 已在书架")
  98. }
  99. val baseUrl = NetworkUtils.getBaseUrl(bookUrl)
  100. ?: throw NoStackTraceException("书籍地址格式不对")
  101. val urlMatcher = AnalyzeUrl.paramPattern.matcher(bookUrl)
  102. if (urlMatcher.find()) {
  103. val origin = GSON.fromJsonObject<AnalyzeUrl.UrlOption>(
  104. bookUrl.substring(urlMatcher.end())
  105. ).getOrNull()?.getOrigin()
  106. origin?.let {
  107. val source = appDb.bookSourceDao.getBookSource(it)
  108. source?.let {
  109. getBookInfo(bookUrl, source)?.let { book ->
  110. return@execute book
  111. }
  112. }
  113. }
  114. }
  115. appDb.bookSourceDao.getBookSourceAddBook(baseUrl)?.let { source ->
  116. getBookInfo(bookUrl, source)?.let { book ->
  117. return@execute book
  118. }
  119. }
  120. appDb.bookSourceDao.hasBookUrlPattern.forEach { source ->
  121. try {
  122. val bs = source.getBookSource()!!
  123. if (bookUrl.matches(bs.bookUrlPattern!!.toRegex())) {
  124. getBookInfo(bookUrl, bs)?.let { book ->
  125. return@execute book
  126. }
  127. }
  128. } catch (_: Exception) {
  129. }
  130. }
  131. throw NoStackTraceException("未找到匹配书源")
  132. }.onError {
  133. AppLog.put("添加书籍 $bookUrl 出错", it)
  134. loadErrorLiveData.postValue(it.localizedMessage)
  135. }.onSuccess {
  136. book = it
  137. success.invoke(it)
  138. }.onStart {
  139. loadStateLiveData.postValue(true)
  140. }.onFinally {
  141. loadStateLiveData.postValue(false)
  142. }
  143. }
  144. private suspend fun getBookInfo(bookUrl: String, source: BookSource): Book? {
  145. return kotlin.runCatching {
  146. val book = Book(
  147. bookUrl = bookUrl,
  148. origin = source.bookSourceUrl,
  149. originName = source.bookSourceName
  150. )
  151. WebBook.getBookInfoAwait(source, book)
  152. }.getOrNull()
  153. }
  154. fun saveSearchBook(book: Book, success: () -> Unit) {
  155. execute {
  156. val searchBook = book.toSearchBook()
  157. appDb.searchBookDao.insert(searchBook)
  158. searchBook
  159. }.onSuccess {
  160. success.invoke()
  161. }
  162. }
  163. }
  164. }