添加单章换源

pull/1649/head
kunfei 3 years ago
parent 4347ff7c78
commit c9cf9b5336
  1. 53
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceDialog.kt
  2. 24
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceViewModel.kt
  3. 11
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterTocAdapter.kt
  4. 3
      app/src/main/res/layout/dialog_chapter_change_source.xml

@ -8,7 +8,6 @@ import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
@ -17,6 +16,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.DialogChapterChangeSourceBinding import io.legado.app.databinding.DialogChapterChangeSourceBinding
@ -33,7 +33,8 @@ import kotlinx.coroutines.launch
class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_change_source), class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_change_source),
Toolbar.OnMenuItemClickListener, Toolbar.OnMenuItemClickListener,
ChangeChapterSourceAdapter.CallBack { ChangeChapterSourceAdapter.CallBack,
ChangeChapterTocAdapter.Callback {
constructor(name: String, author: String, chapterIndex: Int, chapterTitle: String) : this() { constructor(name: String, author: String, chapterIndex: Int, chapterTitle: String) : this() {
arguments = Bundle().apply { arguments = Bundle().apply {
@ -48,11 +49,17 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
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: ChangeChapterSourceViewModel by viewModels() private val viewModel: ChangeChapterSourceViewModel by viewModels()
private val adapter by lazy { ChangeChapterSourceAdapter(requireContext(), viewModel, this) }
private val editSourceResult = private val editSourceResult =
registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) { registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) {
viewModel.startSearch() viewModel.startSearch()
} }
private val searchBookAdapter by lazy {
ChangeChapterSourceAdapter(requireContext(), viewModel, this)
}
private val tocAdapter by lazy {
ChangeChapterTocAdapter(requireContext(), this)
}
private var searchBook: SearchBook? = null
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
@ -64,14 +71,14 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
viewModel.initData(arguments) viewModel.initData(arguments)
showTitle() showTitle()
initMenu() initMenu()
initView()
initRecyclerView() initRecyclerView()
initSearchView() initSearchView()
initLiveData() initLiveData()
} }
private fun showTitle() { private fun showTitle() {
binding.toolBar.title = viewModel.name binding.toolBar.title = viewModel.chapterTitle
binding.toolBar.subtitle = viewModel.author
} }
private fun initMenu() { private fun initMenu() {
@ -86,11 +93,16 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
?.isChecked = AppConfig.changeSourceLoadToc ?.isChecked = AppConfig.changeSourceLoadToc
} }
private fun initView() {
binding.ivHideToc.setOnClickListener {
binding.clToc.gone()
}
}
private fun initRecyclerView() { private fun initRecyclerView() {
binding.recyclerView.layoutManager = LinearLayoutManager(context)
binding.recyclerView.addItemDecoration(VerticalDivider(requireContext())) binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = searchBookAdapter
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { searchBookAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
if (positionStart == 0) { if (positionStart == 0) {
binding.recyclerView.scrollToPosition(0) binding.recyclerView.scrollToPosition(0)
@ -103,6 +115,7 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
} }
} }
}) })
binding.recyclerViewToc.adapter = tocAdapter
} }
private fun initSearchView() { private fun initSearchView() {
@ -147,7 +160,7 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
launch { launch {
viewModel.searchDataFlow viewModel.searchDataFlow
.collect { .collect {
adapter.setItems(it) searchBookAdapter.setItems(it)
delay(1000) delay(1000)
} }
} }
@ -198,8 +211,22 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
return false return false
} }
private val tocSuccess: (toc: List<BookChapter>) -> Unit = {
tocAdapter.durChapterIndex = viewModel.chapterIndex
binding.loadingToc.hide()
tocAdapter.setItems(it)
binding.recyclerViewToc.scrollToPosition(tocAdapter.durChapterIndex - 5)
}
override fun openToc(searchBook: SearchBook) { override fun openToc(searchBook: SearchBook) {
this.searchBook = searchBook
tocAdapter.setItems(null)
binding.clToc.visible() binding.clToc.visible()
binding.loadingToc.show()
viewModel.getToc(searchBook, tocSuccess) {
binding.clToc.gone()
toastOnUi(it)
}
} }
override val bookUrl: String? override val bookUrl: String?
@ -232,6 +259,10 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
} }
} }
override fun clickChapter(bookChapter: BookChapter) {
TODO("Not yet implemented")
}
private fun changeSource(searchBook: SearchBook) { private fun changeSource(searchBook: SearchBook) {
try { try {
val book = searchBook.toBook() val book = searchBook.toBook()
@ -272,9 +303,9 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
override fun observeLiveBus() { override fun observeLiveBus() {
observeEvent<String>(EventBus.SOURCE_CHANGED) { observeEvent<String>(EventBus.SOURCE_CHANGED) {
adapter.notifyItemRangeChanged( searchBookAdapter.notifyItemRangeChanged(
0, 0,
adapter.itemCount, searchBookAdapter.itemCount,
bundleOf(Pair("upCurSource", bookUrl)) bundleOf(Pair("upCurSource", bookUrl))
) )
} }

@ -16,6 +16,7 @@ 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.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.model.NoStackTraceException
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
@ -270,6 +271,29 @@ class ChangeChapterSourceViewModel(application: Application) : BaseViewModel(app
searchPool?.close() searchPool?.close()
} }
fun getToc(
searchBook: SearchBook,
success: (toc: List<BookChapter>) -> Unit,
error: (msg: String) -> Unit
) {
execute {
return@execute tocMap[searchBook.bookUrl]
?: let {
val book = searchBook.toBook()
val source = appDb.bookSourceDao.getBookSource(book.origin)
?: throw NoStackTraceException("书源不存在")
if (book.tocUrl.isEmpty()) {
WebBook.getBookInfoAwait(this, source, book)
}
WebBook.getChapterListAwait(this, source, book)
}
}.onSuccess {
success(it)
}.onError {
error(it.localizedMessage ?: "获取目录出错")
}
}
fun disableSource(searchBook: SearchBook) { fun disableSource(searchBook: SearchBook) {
execute { execute {
appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source -> appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source ->

@ -5,7 +5,6 @@ import android.view.ViewGroup
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter import io.legado.app.base.adapter.RecyclerAdapter
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.databinding.ItemChapterListBinding import io.legado.app.databinding.ItemChapterListBinding
import io.legado.app.lib.theme.ThemeUtils import io.legado.app.lib.theme.ThemeUtils
@ -17,6 +16,8 @@ import io.legado.app.utils.visible
class ChangeChapterTocAdapter(context: Context, val callback: Callback) : class ChangeChapterTocAdapter(context: Context, val callback: Callback) :
RecyclerAdapter<BookChapter, ItemChapterListBinding>(context) { RecyclerAdapter<BookChapter, ItemChapterListBinding>(context) {
var durChapterIndex = 0
override fun getViewBinding(parent: ViewGroup): ItemChapterListBinding { override fun getViewBinding(parent: ViewGroup): ItemChapterListBinding {
return ItemChapterListBinding.inflate(inflater, parent, false) return ItemChapterListBinding.inflate(inflater, parent, false)
} }
@ -28,7 +29,7 @@ class ChangeChapterTocAdapter(context: Context, val callback: Callback) :
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
binding.run { binding.run {
val isDur = callback.durChapterIndex() == item.index val isDur = durChapterIndex == item.index
if (isDur) { if (isDur) {
tvChapterName.setTextColor(context.accentColor) tvChapterName.setTextColor(context.accentColor)
} else { } else {
@ -58,14 +59,12 @@ class ChangeChapterTocAdapter(context: Context, val callback: Callback) :
override fun registerListener(holder: ItemViewHolder, binding: ItemChapterListBinding) { override fun registerListener(holder: ItemViewHolder, binding: ItemChapterListBinding) {
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {
getItem(holder.layoutPosition)?.let { getItem(holder.layoutPosition)?.let {
callback.openChapter(it) callback.clickChapter(it)
} }
} }
} }
interface Callback { interface Callback {
val book: Book? fun clickChapter(bookChapter: BookChapter)
fun openChapter(bookChapter: BookChapter)
fun durChapterIndex(): Int
} }
} }

@ -29,6 +29,7 @@
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/refresh_progress_bar" /> app:layout_constraintTop_toBottomOf="@+id/refresh_progress_bar" />
@ -53,10 +54,12 @@
android:id="@+id/recycler_view_toc" android:id="@+id/recycler_view_toc"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/iv_hide_toc" /> app:layout_constraintTop_toBottomOf="@+id/iv_hide_toc" />
<io.legado.app.ui.widget.anima.RotateLoading <io.legado.app.ui.widget.anima.RotateLoading
android:id="@+id/loading_toc"
android:layout_width="36dp" android:layout_width="36dp"
android:layout_height="36dp" android:layout_height="36dp"
android:visibility="gone" android:visibility="gone"

Loading…
Cancel
Save