pull/1649/head
parent
439fd5b5bf
commit
7a71ffe67e
@ -0,0 +1,122 @@ |
|||||||
|
package io.legado.app.ui.book.changesource |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.os.Bundle |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.appcompat.widget.PopupMenu |
||||||
|
import androidx.recyclerview.widget.DiffUtil |
||||||
|
import io.legado.app.R |
||||||
|
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.utils.invisible |
||||||
|
import io.legado.app.utils.visible |
||||||
|
import splitties.views.onLongClick |
||||||
|
|
||||||
|
|
||||||
|
class ChangeChapterSourceAdapter( |
||||||
|
context: Context, |
||||||
|
val viewModel: ChangeBookSourceViewModel, |
||||||
|
val callBack: CallBack |
||||||
|
) : DiffRecyclerAdapter<SearchBook, ItemChangeSourceBinding>(context) { |
||||||
|
|
||||||
|
override val diffItemCallback = object : DiffUtil.ItemCallback<SearchBook>() { |
||||||
|
override fun areItemsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean { |
||||||
|
return oldItem.bookUrl == newItem.bookUrl |
||||||
|
} |
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean { |
||||||
|
return oldItem.originName == newItem.originName |
||||||
|
&& oldItem.getDisplayLastChapterTitle() == newItem.getDisplayLastChapterTitle() |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun getViewBinding(parent: ViewGroup): ItemChangeSourceBinding { |
||||||
|
return ItemChangeSourceBinding.inflate(inflater, parent, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun convert( |
||||||
|
holder: ItemViewHolder, |
||||||
|
binding: ItemChangeSourceBinding, |
||||||
|
item: SearchBook, |
||||||
|
payloads: MutableList<Any> |
||||||
|
) { |
||||||
|
val bundle = payloads.getOrNull(0) as? Bundle |
||||||
|
binding.apply { |
||||||
|
if (bundle == null) { |
||||||
|
tvOrigin.text = item.originName |
||||||
|
tvAuthor.text = item.author |
||||||
|
tvLast.text = item.getDisplayLastChapterTitle() |
||||||
|
if (callBack.bookUrl == item.bookUrl) { |
||||||
|
ivChecked.visible() |
||||||
|
} else { |
||||||
|
ivChecked.invisible() |
||||||
|
} |
||||||
|
} else { |
||||||
|
bundle.keySet().map { |
||||||
|
when (it) { |
||||||
|
"name" -> tvOrigin.text = item.originName |
||||||
|
"latest" -> tvLast.text = item.getDisplayLastChapterTitle() |
||||||
|
"upCurSource" -> if (callBack.bookUrl == item.bookUrl) { |
||||||
|
ivChecked.visible() |
||||||
|
} else { |
||||||
|
ivChecked.invisible() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun registerListener(holder: ItemViewHolder, binding: ItemChangeSourceBinding) { |
||||||
|
holder.itemView.setOnClickListener { |
||||||
|
getItem(holder.layoutPosition)?.let { |
||||||
|
callBack.changeTo(it) |
||||||
|
} |
||||||
|
} |
||||||
|
holder.itemView.onLongClick { |
||||||
|
showMenu(holder.itemView, getItem(holder.layoutPosition)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun showMenu(view: View, searchBook: SearchBook?) { |
||||||
|
searchBook ?: return |
||||||
|
val popupMenu = PopupMenu(context, view) |
||||||
|
popupMenu.inflate(R.menu.change_source_item) |
||||||
|
popupMenu.setOnMenuItemClickListener { |
||||||
|
when (it.itemId) { |
||||||
|
R.id.menu_top_source -> { |
||||||
|
callBack.topSource(searchBook) |
||||||
|
} |
||||||
|
R.id.menu_bottom_source -> { |
||||||
|
callBack.bottomSource(searchBook) |
||||||
|
} |
||||||
|
R.id.menu_edit_source -> { |
||||||
|
callBack.editSource(searchBook) |
||||||
|
} |
||||||
|
R.id.menu_disable_source -> { |
||||||
|
callBack.disableSource(searchBook) |
||||||
|
} |
||||||
|
R.id.menu_delete_source -> { |
||||||
|
callBack.deleteSource(searchBook) |
||||||
|
updateItems(0, itemCount, listOf<Int>()) |
||||||
|
} |
||||||
|
} |
||||||
|
true |
||||||
|
} |
||||||
|
popupMenu.show() |
||||||
|
} |
||||||
|
|
||||||
|
interface CallBack { |
||||||
|
val bookUrl: String? |
||||||
|
fun changeTo(searchBook: SearchBook) |
||||||
|
fun topSource(searchBook: SearchBook) |
||||||
|
fun bottomSource(searchBook: SearchBook) |
||||||
|
fun editSource(searchBook: SearchBook) |
||||||
|
fun disableSource(searchBook: SearchBook) |
||||||
|
fun deleteSource(searchBook: SearchBook) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,287 @@ |
|||||||
|
package io.legado.app.ui.book.changesource |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.Menu |
||||||
|
import android.view.MenuItem |
||||||
|
import android.view.View |
||||||
|
import androidx.appcompat.widget.SearchView |
||||||
|
import androidx.appcompat.widget.Toolbar |
||||||
|
import androidx.core.os.bundleOf |
||||||
|
import androidx.fragment.app.viewModels |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import androidx.recyclerview.widget.RecyclerView |
||||||
|
import io.legado.app.R |
||||||
|
import io.legado.app.base.BaseDialogFragment |
||||||
|
import io.legado.app.constant.AppPattern |
||||||
|
import io.legado.app.constant.EventBus |
||||||
|
import io.legado.app.constant.PreferKey |
||||||
|
import io.legado.app.data.appDb |
||||||
|
import io.legado.app.data.entities.Book |
||||||
|
import io.legado.app.data.entities.BookSource |
||||||
|
import io.legado.app.data.entities.SearchBook |
||||||
|
import io.legado.app.databinding.DialogChangeSourceBinding |
||||||
|
import io.legado.app.help.AppConfig |
||||||
|
import io.legado.app.lib.theme.primaryColor |
||||||
|
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.viewbindingdelegate.viewBinding |
||||||
|
import kotlinx.coroutines.delay |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
|
||||||
|
|
||||||
|
class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_change_source), |
||||||
|
Toolbar.OnMenuItemClickListener, |
||||||
|
ChangeBookSourceAdapter.CallBack { |
||||||
|
|
||||||
|
constructor(name: String, author: String) : this() { |
||||||
|
arguments = Bundle().apply { |
||||||
|
putString("name", name) |
||||||
|
putString("author", author) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private val binding by viewBinding(DialogChangeSourceBinding::bind) |
||||||
|
private val groups = linkedSetOf<String>() |
||||||
|
private val callBack: CallBack? get() = activity as? CallBack |
||||||
|
private val viewModel: ChangeBookSourceViewModel by viewModels() |
||||||
|
private val adapter by lazy { ChangeBookSourceAdapter(requireContext(), viewModel, this) } |
||||||
|
private val editSourceResult = |
||||||
|
registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) { |
||||||
|
viewModel.startSearch() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onStart() { |
||||||
|
super.onStart() |
||||||
|
setLayout(0.9f, 0.9f) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
binding.toolBar.setBackgroundColor(primaryColor) |
||||||
|
viewModel.initData(arguments) |
||||||
|
showTitle() |
||||||
|
initMenu() |
||||||
|
initRecyclerView() |
||||||
|
initSearchView() |
||||||
|
initLiveData() |
||||||
|
} |
||||||
|
|
||||||
|
private fun showTitle() { |
||||||
|
binding.toolBar.title = viewModel.name |
||||||
|
binding.toolBar.subtitle = viewModel.author |
||||||
|
} |
||||||
|
|
||||||
|
private fun initMenu() { |
||||||
|
binding.toolBar.inflateMenu(R.menu.change_source) |
||||||
|
binding.toolBar.menu.applyTint(requireContext()) |
||||||
|
binding.toolBar.setOnMenuItemClickListener(this) |
||||||
|
binding.toolBar.menu.findItem(R.id.menu_check_author) |
||||||
|
?.isChecked = AppConfig.changeSourceCheckAuthor |
||||||
|
binding.toolBar.menu.findItem(R.id.menu_load_info) |
||||||
|
?.isChecked = AppConfig.changeSourceLoadInfo |
||||||
|
binding.toolBar.menu.findItem(R.id.menu_load_toc) |
||||||
|
?.isChecked = AppConfig.changeSourceLoadToc |
||||||
|
} |
||||||
|
|
||||||
|
private fun initRecyclerView() { |
||||||
|
binding.recyclerView.layoutManager = LinearLayoutManager(context) |
||||||
|
binding.recyclerView.addItemDecoration(VerticalDivider(requireContext())) |
||||||
|
binding.recyclerView.adapter = adapter |
||||||
|
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { |
||||||
|
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { |
||||||
|
if (positionStart == 0) { |
||||||
|
binding.recyclerView.scrollToPosition(0) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { |
||||||
|
if (toPosition == 0) { |
||||||
|
binding.recyclerView.scrollToPosition(0) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
private fun initSearchView() { |
||||||
|
val searchView = binding.toolBar.menu.findItem(R.id.menu_screen).actionView as SearchView |
||||||
|
searchView.setOnCloseListener { |
||||||
|
showTitle() |
||||||
|
false |
||||||
|
} |
||||||
|
searchView.setOnSearchClickListener { |
||||||
|
binding.toolBar.title = "" |
||||||
|
binding.toolBar.subtitle = "" |
||||||
|
} |
||||||
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { |
||||||
|
override fun onQueryTextSubmit(query: String?): Boolean { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String?): Boolean { |
||||||
|
viewModel.screen(newText) |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
private fun initLiveData() { |
||||||
|
viewModel.searchStateData.observe(viewLifecycleOwner) { |
||||||
|
binding.refreshProgressBar.isAutoLoading = it |
||||||
|
if (it) { |
||||||
|
startStopMenuItem?.let { item -> |
||||||
|
item.setIcon(R.drawable.ic_stop_black_24dp) |
||||||
|
item.setTitle(R.string.stop) |
||||||
|
} |
||||||
|
} else { |
||||||
|
startStopMenuItem?.let { item -> |
||||||
|
item.setIcon(R.drawable.ic_refresh_black_24dp) |
||||||
|
item.setTitle(R.string.refresh) |
||||||
|
} |
||||||
|
} |
||||||
|
binding.toolBar.menu.applyTint(requireContext()) |
||||||
|
} |
||||||
|
launch { |
||||||
|
viewModel.searchDataFlow |
||||||
|
.collect { |
||||||
|
adapter.setItems(it) |
||||||
|
delay(1000) |
||||||
|
} |
||||||
|
} |
||||||
|
launch { |
||||||
|
appDb.bookSourceDao.flowGroupEnabled().collect { |
||||||
|
groups.clear() |
||||||
|
it.map { group -> |
||||||
|
groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex)) |
||||||
|
} |
||||||
|
upGroupMenu() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private val startStopMenuItem: MenuItem? |
||||||
|
get() = binding.toolBar.menu.findItem(R.id.menu_start_stop) |
||||||
|
|
||||||
|
override fun onMenuItemClick(item: MenuItem?): Boolean { |
||||||
|
when (item?.itemId) { |
||||||
|
R.id.menu_check_author -> { |
||||||
|
AppConfig.changeSourceCheckAuthor = !item.isChecked |
||||||
|
item.isChecked = !item.isChecked |
||||||
|
viewModel.refresh() |
||||||
|
} |
||||||
|
R.id.menu_load_toc -> { |
||||||
|
putPrefBoolean(PreferKey.changeSourceLoadToc, !item.isChecked) |
||||||
|
item.isChecked = !item.isChecked |
||||||
|
} |
||||||
|
R.id.menu_load_info -> { |
||||||
|
putPrefBoolean(PreferKey.changeSourceLoadInfo, !item.isChecked) |
||||||
|
item.isChecked = !item.isChecked |
||||||
|
} |
||||||
|
R.id.menu_start_stop -> viewModel.startOrStopSearch() |
||||||
|
R.id.menu_source_manage -> startActivity<BookSourceActivity>() |
||||||
|
else -> if (item?.groupId == R.id.source_group) { |
||||||
|
if (!item.isChecked) { |
||||||
|
item.isChecked = true |
||||||
|
if (item.title.toString() == getString(R.string.all_source)) { |
||||||
|
putPrefString("searchGroup", "") |
||||||
|
} else { |
||||||
|
putPrefString("searchGroup", item.title.toString()) |
||||||
|
} |
||||||
|
viewModel.startOrStopSearch() |
||||||
|
viewModel.refresh() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
override fun changeTo(searchBook: SearchBook) { |
||||||
|
changeSource(searchBook) |
||||||
|
dismissAllowingStateLoss() |
||||||
|
} |
||||||
|
|
||||||
|
override val bookUrl: String? |
||||||
|
get() = callBack?.oldBook?.bookUrl |
||||||
|
|
||||||
|
override fun topSource(searchBook: SearchBook) { |
||||||
|
viewModel.topSource(searchBook) |
||||||
|
} |
||||||
|
|
||||||
|
override fun bottomSource(searchBook: SearchBook) { |
||||||
|
viewModel.bottomSource(searchBook) |
||||||
|
} |
||||||
|
|
||||||
|
override fun editSource(searchBook: SearchBook) { |
||||||
|
editSourceResult.launch { |
||||||
|
putExtra("sourceUrl", searchBook.origin) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun disableSource(searchBook: SearchBook) { |
||||||
|
viewModel.disableSource(searchBook) |
||||||
|
} |
||||||
|
|
||||||
|
override fun deleteSource(searchBook: SearchBook) { |
||||||
|
viewModel.del(searchBook) |
||||||
|
if (bookUrl == searchBook.bookUrl) { |
||||||
|
viewModel.firstSourceOrNull(searchBook)?.let { |
||||||
|
changeSource(it) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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}") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 更新分组菜单 |
||||||
|
*/ |
||||||
|
private fun upGroupMenu() { |
||||||
|
val menu: Menu = binding.toolBar.menu |
||||||
|
val selectedGroup = getPrefString("searchGroup") |
||||||
|
menu.removeGroup(R.id.source_group) |
||||||
|
val allItem = menu.add(R.id.source_group, Menu.NONE, Menu.NONE, R.string.all_source) |
||||||
|
var hasSelectedGroup = false |
||||||
|
groups.sortedWith { o1, o2 -> |
||||||
|
o1.cnCompare(o2) |
||||||
|
}.forEach { group -> |
||||||
|
menu.add(R.id.source_group, Menu.NONE, Menu.NONE, group)?.let { |
||||||
|
if (group == selectedGroup) { |
||||||
|
it.isChecked = true |
||||||
|
hasSelectedGroup = true |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
menu.setGroupCheckable(R.id.source_group, true, true) |
||||||
|
if (!hasSelectedGroup) { |
||||||
|
allItem.isChecked = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun observeLiveBus() { |
||||||
|
observeEvent<String>(EventBus.SOURCE_CHANGED) { |
||||||
|
adapter.notifyItemRangeChanged( |
||||||
|
0, |
||||||
|
adapter.itemCount, |
||||||
|
bundleOf(Pair("upCurSource", bookUrl)) |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
interface CallBack { |
||||||
|
val oldBook: Book? |
||||||
|
fun changeTo(source: BookSource, book: Book) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,327 @@ |
|||||||
|
package io.legado.app.ui.book.changesource |
||||||
|
|
||||||
|
import android.app.Application |
||||||
|
import android.os.Bundle |
||||||
|
import androidx.lifecycle.MutableLiveData |
||||||
|
import androidx.lifecycle.viewModelScope |
||||||
|
import cn.hutool.core.collection.ConcurrentHashSet |
||||||
|
import io.legado.app.base.BaseViewModel |
||||||
|
import io.legado.app.constant.AppConst |
||||||
|
import io.legado.app.constant.AppPattern |
||||||
|
import io.legado.app.constant.PreferKey |
||||||
|
import io.legado.app.data.appDb |
||||||
|
import io.legado.app.data.entities.Book |
||||||
|
import io.legado.app.data.entities.BookSource |
||||||
|
import io.legado.app.data.entities.SearchBook |
||||||
|
import io.legado.app.help.AppConfig |
||||||
|
import io.legado.app.help.coroutine.CompositeCoroutine |
||||||
|
import io.legado.app.model.webBook.WebBook |
||||||
|
import io.legado.app.utils.getPrefBoolean |
||||||
|
import io.legado.app.utils.getPrefString |
||||||
|
import kotlinx.coroutines.Dispatchers.IO |
||||||
|
import kotlinx.coroutines.ExecutorCoroutineDispatcher |
||||||
|
import kotlinx.coroutines.asCoroutineDispatcher |
||||||
|
import kotlinx.coroutines.channels.awaitClose |
||||||
|
import kotlinx.coroutines.flow.callbackFlow |
||||||
|
import kotlinx.coroutines.flow.conflate |
||||||
|
import kotlinx.coroutines.flow.flowOn |
||||||
|
import kotlinx.coroutines.flow.map |
||||||
|
import splitties.init.appCtx |
||||||
|
import timber.log.Timber |
||||||
|
import java.util.concurrent.Executors |
||||||
|
import kotlin.math.min |
||||||
|
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate") |
||||||
|
class ChangeChapterSourceViewModel(application: Application) : BaseViewModel(application) { |
||||||
|
private val threadCount = AppConfig.threadCount |
||||||
|
private var searchPool: ExecutorCoroutineDispatcher? = null |
||||||
|
val searchStateData = MutableLiveData<Boolean>() |
||||||
|
var name: String = "" |
||||||
|
var author: String = "" |
||||||
|
private var tasks = CompositeCoroutine() |
||||||
|
private var screenKey: String = "" |
||||||
|
private var bookSourceList = arrayListOf<BookSource>() |
||||||
|
private val searchBooks = ConcurrentHashSet<SearchBook>() |
||||||
|
private val searchGroup get() = appCtx.getPrefString("searchGroup") ?: "" |
||||||
|
private var searchCallback: SourceCallback? = null |
||||||
|
val searchDataFlow = callbackFlow { |
||||||
|
|
||||||
|
searchCallback = object : SourceCallback { |
||||||
|
|
||||||
|
override fun searchSuccess(searchBook: SearchBook) { |
||||||
|
appDb.searchBookDao.insert(searchBook) |
||||||
|
when { |
||||||
|
screenKey.isEmpty() -> searchBooks.add(searchBook) |
||||||
|
searchBook.name.contains(screenKey) -> searchBooks.add(searchBook) |
||||||
|
else -> return |
||||||
|
} |
||||||
|
trySend(searchBooks) |
||||||
|
} |
||||||
|
|
||||||
|
override fun upAdapter() { |
||||||
|
trySend(searchBooks) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
getDbSearchBooks().let { |
||||||
|
searchBooks.clear() |
||||||
|
searchBooks.addAll(it) |
||||||
|
trySend(searchBooks) |
||||||
|
} |
||||||
|
|
||||||
|
if (searchBooks.size <= 1) { |
||||||
|
startSearch() |
||||||
|
} |
||||||
|
|
||||||
|
awaitClose { |
||||||
|
searchCallback = null |
||||||
|
} |
||||||
|
}.conflate() |
||||||
|
.map { |
||||||
|
searchBooks.sortedBy { it.originOrder } |
||||||
|
}.flowOn(IO) |
||||||
|
|
||||||
|
@Volatile |
||||||
|
private var searchIndex = -1 |
||||||
|
|
||||||
|
fun initData(arguments: Bundle?) { |
||||||
|
arguments?.let { bundle -> |
||||||
|
bundle.getString("name")?.let { |
||||||
|
name = it |
||||||
|
} |
||||||
|
bundle.getString("author")?.let { |
||||||
|
author = it.replace(AppPattern.authorRegex, "") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun initSearchPool() { |
||||||
|
searchPool = Executors |
||||||
|
.newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher() |
||||||
|
searchIndex = -1 |
||||||
|
} |
||||||
|
|
||||||
|
fun refresh() { |
||||||
|
getDbSearchBooks().let { |
||||||
|
searchBooks.clear() |
||||||
|
searchBooks.addAll(it) |
||||||
|
searchCallback?.upAdapter() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun startSearch() { |
||||||
|
execute { |
||||||
|
stopSearch() |
||||||
|
appDb.searchBookDao.clear(name, author) |
||||||
|
searchBooks.clear() |
||||||
|
bookSourceList.clear() |
||||||
|
if (searchGroup.isBlank()) { |
||||||
|
bookSourceList.addAll(appDb.bookSourceDao.allEnabled) |
||||||
|
} else { |
||||||
|
val sources = appDb.bookSourceDao.getEnabledByGroup(searchGroup) |
||||||
|
if (sources.isEmpty()) { |
||||||
|
bookSourceList.addAll(appDb.bookSourceDao.allEnabled) |
||||||
|
} else { |
||||||
|
bookSourceList.addAll(sources) |
||||||
|
} |
||||||
|
} |
||||||
|
searchStateData.postValue(true) |
||||||
|
initSearchPool() |
||||||
|
for (i in 0 until threadCount) { |
||||||
|
search() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun search() { |
||||||
|
synchronized(this) { |
||||||
|
if (searchIndex >= bookSourceList.lastIndex) { |
||||||
|
return |
||||||
|
} |
||||||
|
searchIndex++ |
||||||
|
} |
||||||
|
val source = bookSourceList[searchIndex] |
||||||
|
val task = WebBook |
||||||
|
.searchBook(viewModelScope, source, name, context = searchPool!!) |
||||||
|
.timeout(60000L) |
||||||
|
.onSuccess(IO) { |
||||||
|
it.forEach { searchBook -> |
||||||
|
if (searchBook.name == name) { |
||||||
|
if ((AppConfig.changeSourceCheckAuthor && searchBook.author.contains(author)) |
||||||
|
|| !AppConfig.changeSourceCheckAuthor |
||||||
|
) { |
||||||
|
if (searchBook.latestChapterTitle.isNullOrEmpty()) { |
||||||
|
if (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) { |
||||||
|
loadBookInfo(source, searchBook.toBook()) |
||||||
|
} else { |
||||||
|
searchCallback?.searchSuccess(searchBook) |
||||||
|
} |
||||||
|
} else { |
||||||
|
searchCallback?.searchSuccess(searchBook) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.onFinally(searchPool) { |
||||||
|
synchronized(this) { |
||||||
|
if (searchIndex < bookSourceList.lastIndex) { |
||||||
|
search() |
||||||
|
} else { |
||||||
|
searchIndex++ |
||||||
|
} |
||||||
|
if (searchIndex >= bookSourceList.lastIndex + bookSourceList.size |
||||||
|
|| searchIndex >= bookSourceList.lastIndex + threadCount |
||||||
|
) { |
||||||
|
searchStateData.postValue(false) |
||||||
|
tasks.clear() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
tasks.add(task) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private fun loadBookInfo(source: BookSource, book: Book) { |
||||||
|
WebBook.getBookInfo(viewModelScope, source, book, context = searchPool!!) |
||||||
|
.onSuccess(IO) { |
||||||
|
if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { |
||||||
|
loadBookToc(source, book) |
||||||
|
} else { |
||||||
|
//从详情页里获取最新章节 |
||||||
|
book.latestChapterTitle = it.latestChapterTitle |
||||||
|
val searchBook = book.toSearchBook() |
||||||
|
searchCallback?.searchSuccess(searchBook) |
||||||
|
} |
||||||
|
}.onError(IO) { |
||||||
|
Timber.e(it) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun loadBookToc(source: BookSource, book: Book) { |
||||||
|
WebBook.getChapterList(viewModelScope, source, book, context = searchPool!!) |
||||||
|
.onSuccess(IO) { chapters -> |
||||||
|
book.latestChapterTitle = chapters.last().title |
||||||
|
val searchBook: SearchBook = book.toSearchBook() |
||||||
|
searchCallback?.searchSuccess(searchBook) |
||||||
|
}.onError(IO) { |
||||||
|
Timber.e(it) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun getDbSearchBooks(): List<SearchBook> { |
||||||
|
return if (screenKey.isEmpty()) { |
||||||
|
if (AppConfig.changeSourceCheckAuthor) { |
||||||
|
appDb.searchBookDao.getChangeSourceSearch(name, author, searchGroup) |
||||||
|
} else { |
||||||
|
appDb.searchBookDao.getChangeSourceSearch(name, "", searchGroup) |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (AppConfig.changeSourceCheckAuthor) { |
||||||
|
appDb.searchBookDao.getChangeSourceSearch(name, author, screenKey, searchGroup) |
||||||
|
} else { |
||||||
|
appDb.searchBookDao.getChangeSourceSearch(name, "", screenKey, searchGroup) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 筛选 |
||||||
|
*/ |
||||||
|
fun screen(key: String?) { |
||||||
|
screenKey = key?.trim() ?: "" |
||||||
|
execute { |
||||||
|
getDbSearchBooks().let { |
||||||
|
searchBooks.clear() |
||||||
|
searchBooks.addAll(it) |
||||||
|
searchCallback?.upAdapter() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun startOrStopSearch() { |
||||||
|
if (tasks.isEmpty) { |
||||||
|
startSearch() |
||||||
|
} else { |
||||||
|
stopSearch() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun stopSearch() { |
||||||
|
tasks.clear() |
||||||
|
searchPool?.close() |
||||||
|
searchStateData.postValue(false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCleared() { |
||||||
|
super.onCleared() |
||||||
|
searchPool?.close() |
||||||
|
} |
||||||
|
|
||||||
|
fun disableSource(searchBook: SearchBook) { |
||||||
|
execute { |
||||||
|
appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source -> |
||||||
|
source.enabled = false |
||||||
|
appDb.bookSourceDao.update(source) |
||||||
|
} |
||||||
|
searchBooks.remove(searchBook) |
||||||
|
searchCallback?.upAdapter() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun topSource(searchBook: SearchBook) { |
||||||
|
execute { |
||||||
|
appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source -> |
||||||
|
val minOrder = appDb.bookSourceDao.minOrder - 1 |
||||||
|
source.customOrder = minOrder |
||||||
|
searchBook.originOrder = source.customOrder |
||||||
|
appDb.bookSourceDao.update(source) |
||||||
|
updateSource(searchBook) |
||||||
|
} |
||||||
|
searchCallback?.upAdapter() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun bottomSource(searchBook: SearchBook) { |
||||||
|
execute { |
||||||
|
appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source -> |
||||||
|
val maxOrder = appDb.bookSourceDao.maxOrder + 1 |
||||||
|
source.customOrder = maxOrder |
||||||
|
searchBook.originOrder = source.customOrder |
||||||
|
appDb.bookSourceDao.update(source) |
||||||
|
updateSource(searchBook) |
||||||
|
} |
||||||
|
searchCallback?.upAdapter() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun updateSource(searchBook: SearchBook) { |
||||||
|
appDb.searchBookDao.update(searchBook) |
||||||
|
} |
||||||
|
|
||||||
|
fun del(searchBook: SearchBook) { |
||||||
|
execute { |
||||||
|
appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source -> |
||||||
|
appDb.bookSourceDao.delete(source) |
||||||
|
appDb.searchBookDao.delete(searchBook) |
||||||
|
} |
||||||
|
} |
||||||
|
searchBooks.remove(searchBook) |
||||||
|
searchCallback?.upAdapter() |
||||||
|
} |
||||||
|
|
||||||
|
fun firstSourceOrNull(searchBook: SearchBook): SearchBook? { |
||||||
|
return searchBooks.firstOrNull { it.bookUrl != searchBook.bookUrl } |
||||||
|
} |
||||||
|
|
||||||
|
interface SourceCallback { |
||||||
|
|
||||||
|
fun searchSuccess(searchBook: SearchBook) |
||||||
|
|
||||||
|
fun upAdapter() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue