diff --git a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt index 0c5a6183f..68dc78da3 100644 --- a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt @@ -30,7 +30,7 @@ interface SearchBookDao { t1.wordCount, t2.customOrder as originOrder from searchBooks as t1 inner join book_sources as t2 on t1.origin = t2.bookSourceUrl - where t1.name = :name and t1.author = :author + where t1.name = :name and t1.author like '%'||:author||'%' and originName like '%'||:key||'%' and t2.enabled = 1 and t2.bookSourceGroup like '%'||:sourceGroup||'%' order by t2.customOrder""" diff --git a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt index 798015b75..64bffb3b6 100644 --- a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt @@ -14,7 +14,6 @@ import io.legado.app.utils.applyTint import io.legado.app.utils.setLayout import io.legado.app.utils.viewbindingdelegate.viewBinding import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.launch /** @@ -67,7 +66,6 @@ class ChangeCoverDialog() : BaseDialogFragment(R.layout.dialog_change_cover), private fun initData() { launch { viewModel.dataFlow - .conflate() .collect { adapter.setItems(it) delay(1000) diff --git a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt index 2f86d473b..bc5de6649 100644 --- a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt @@ -18,6 +18,7 @@ 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 java.util.concurrent.Executors import kotlin.math.min @@ -54,7 +55,8 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application searchBooks.clear() searchSuccess = null } - }.flowOn(IO) + }.conflate() + .flowOn(IO) @Volatile private var searchIndex = -1 diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt index 5ee975143..2732356f5 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt @@ -51,7 +51,6 @@ class ChangeSourceDialog() : BaseDialogFragment(R.layout.dialog_change_source), viewModel.startSearch() } - override fun onStart() { super.onStart() setLayout(0.9f, 0.9f) @@ -65,7 +64,6 @@ class ChangeSourceDialog() : BaseDialogFragment(R.layout.dialog_change_source), initRecyclerView() initSearchView() initLiveData() - viewModel.loadDbSearchBook() } private fun showTitle() { @@ -143,10 +141,11 @@ class ChangeSourceDialog() : BaseDialogFragment(R.layout.dialog_change_source), } binding.toolBar.menu.applyTint(requireContext()) } - viewModel.searchBooksLiveData.observe(viewLifecycleOwner) { - adapter.setItems(it) - } launch { + viewModel.searchDataFlow + .collect { + adapter.setItems(it) + } appDb.bookSourceDao.flowGroupEnabled().collect { groups.clear() it.map { group -> @@ -165,7 +164,7 @@ class ChangeSourceDialog() : BaseDialogFragment(R.layout.dialog_change_source), R.id.menu_check_author -> { AppConfig.changeSourceCheckAuthor = !item.isChecked item.isChecked = !item.isChecked - viewModel.loadDbSearchBook() + viewModel.refresh() } R.id.menu_load_toc -> { putPrefBoolean(PreferKey.changeSourceLoadToc, !item.isChecked) @@ -186,7 +185,7 @@ class ChangeSourceDialog() : BaseDialogFragment(R.layout.dialog_change_source), putPrefString("searchGroup", item.title.toString()) } viewModel.startOrStopSearch() - viewModel.loadDbSearchBook() + viewModel.refresh() } } } diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt index 9249edc92..197b3f1de 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt @@ -17,12 +17,18 @@ 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.* import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.ExecutorCoroutineDispatcher +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.delay +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.CopyOnWriteArraySet +import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executors import kotlin.math.min @@ -30,17 +36,56 @@ import kotlin.math.min class ChangeSourceViewModel(application: Application) : BaseViewModel(application) { private val threadCount = AppConfig.threadCount private var searchPool: ExecutorCoroutineDispatcher? = null - private var upAdapterJob: Job? = null val searchStateData = MutableLiveData() - val searchBooksLiveData = MutableLiveData>() var name: String = "" var author: String = "" private var tasks = CompositeCoroutine() private var screenKey: String = "" private var bookSourceList = arrayListOf() - private val searchBooks = CopyOnWriteArraySet() - private var postTime = 0L + private val searchBooks = CopyOnWriteArrayList() 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) + val index = searchBooks.indexOf(searchBook) + when { + index >= 0 -> searchBooks[index] = searchBook + 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 { + val data = searchBooks.sortedBy { it.originOrder } + delay(1000) + data + }.flowOn(IO) @Volatile private var searchIndex = -1 @@ -62,56 +107,19 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio searchIndex = -1 } - fun loadDbSearchBook() { - execute { + fun refresh() { + getDbSearchBooks().let { searchBooks.clear() - upAdapter() - val sbs = if (AppConfig.changeSourceCheckAuthor) { - appDb.searchBookDao.getChangeSourceSearch(name, author, searchGroup) - } else { - appDb.searchBookDao.getChangeSourceSearch(name, "", searchGroup) - } - searchBooks.addAll(sbs) - searchBooksLiveData.postValue(searchBooks.toList()) - if (sbs.size <= 1) { - startSearch() - } + searchBooks.addAll(it) + searchCallback?.upAdapter() } } - @Synchronized - private fun upAdapter() { - if (System.currentTimeMillis() >= postTime + 500) { - upAdapterJob?.cancel() - postTime = System.currentTimeMillis() - val books = searchBooks.toList() - searchBooksLiveData.postValue(books.sortedBy { it.originOrder }) - } else { - upAdapterJob?.cancel() - upAdapterJob = viewModelScope.launch { - delay(500) - upAdapter() - } - } - } - - private fun searchFinish(searchBook: SearchBook) { - if (searchBooks.contains(searchBook)) return - appDb.searchBookDao.insert(searchBook) - if (screenKey.isEmpty()) { - searchBooks.add(searchBook) - } else if (searchBook.name.contains(screenKey)) { - searchBooks.add(searchBook) - } - upAdapter() - } - fun startSearch() { execute { stopSearch() appDb.searchBookDao.clear(name, author) searchBooks.clear() - upAdapter() bookSourceList.clear() if (searchGroup.isBlank()) { bookSourceList.addAll(appDb.bookSourceDao.allEnabled) @@ -152,10 +160,10 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio if (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) { loadBookInfo(source, searchBook.toBook()) } else { - searchFinish(searchBook) + searchCallback?.searchSuccess(searchBook) } } else { - searchFinish(searchBook) + searchCallback?.searchSuccess(searchBook) } } } @@ -190,7 +198,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio //从详情页里获取最新章节 book.latestChapterTitle = it.latestChapterTitle val searchBook = book.toSearchBook() - searchFinish(searchBook) + searchCallback?.searchSuccess(searchBook) } }.onError { Timber.e(it) @@ -202,26 +210,38 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio .onSuccess(IO) { chapters -> book.latestChapterTitle = chapters.last().title val searchBook: SearchBook = book.toSearchBook() - searchFinish(searchBook) + searchCallback?.searchSuccess(searchBook) }.onError { Timber.e(it) } } + private fun getDbSearchBooks(): List { + 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 { - screenKey = key ?: "" - if (key.isNullOrEmpty()) { - loadDbSearchBook() - } else { - val items = - appDb.searchBookDao.getChangeSourceSearch(name, author, screenKey, searchGroup) + getDbSearchBooks().let { searchBooks.clear() - searchBooks.addAll(items) - upAdapter() + searchBooks.addAll(it) + searchCallback?.upAdapter() } } } @@ -252,7 +272,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio appDb.bookSourceDao.update(source) } searchBooks.remove(searchBook) - upAdapter() + searchCallback?.upAdapter() } } @@ -265,7 +285,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio appDb.bookSourceDao.update(source) updateSource(searchBook) } - upAdapter() + searchCallback?.upAdapter() } } @@ -278,7 +298,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio appDb.bookSourceDao.update(source) updateSource(searchBook) } - upAdapter() + searchCallback?.upAdapter() } } @@ -294,11 +314,19 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio } } searchBooks.remove(searchBook) - upAdapter() + searchCallback?.upAdapter() } fun firstSourceOrNull(searchBook: SearchBook): SearchBook? { return searchBooks.firstOrNull { it.bookUrl != searchBook.bookUrl } } + interface SourceCallback { + + fun searchSuccess(searchBook: SearchBook) + + fun upAdapter() + + } + } \ No newline at end of file