pull/1649/head
kunfei 3 years ago
parent 912bee7c05
commit a0c287550f
  1. 2
      app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt
  2. 2
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt
  3. 4
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt
  4. 11
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt
  5. 156
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt

@ -30,7 +30,7 @@ interface SearchBookDao {
t1.wordCount, t2.customOrder as originOrder t1.wordCount, t2.customOrder as originOrder
from searchBooks as t1 inner join book_sources as t2 from searchBooks as t1 inner join book_sources as t2
on t1.origin = t2.bookSourceUrl 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 originName like '%'||:key||'%' and t2.enabled = 1
and t2.bookSourceGroup like '%'||:sourceGroup||'%' and t2.bookSourceGroup like '%'||:sourceGroup||'%'
order by t2.customOrder""" order by t2.customOrder"""

@ -14,7 +14,6 @@ import io.legado.app.utils.applyTint
import io.legado.app.utils.setLayout import io.legado.app.utils.setLayout
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
@ -67,7 +66,6 @@ class ChangeCoverDialog() : BaseDialogFragment(R.layout.dialog_change_cover),
private fun initData() { private fun initData() {
launch { launch {
viewModel.dataFlow viewModel.dataFlow
.conflate()
.collect { .collect {
adapter.setItems(it) adapter.setItems(it)
delay(1000) delay(1000)

@ -18,6 +18,7 @@ import kotlinx.coroutines.ExecutorCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.math.min import kotlin.math.min
@ -54,7 +55,8 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
searchBooks.clear() searchBooks.clear()
searchSuccess = null searchSuccess = null
} }
}.flowOn(IO) }.conflate()
.flowOn(IO)
@Volatile @Volatile
private var searchIndex = -1 private var searchIndex = -1

@ -51,7 +51,6 @@ class ChangeSourceDialog() : BaseDialogFragment(R.layout.dialog_change_source),
viewModel.startSearch() viewModel.startSearch()
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
setLayout(0.9f, 0.9f) setLayout(0.9f, 0.9f)
@ -65,7 +64,6 @@ class ChangeSourceDialog() : BaseDialogFragment(R.layout.dialog_change_source),
initRecyclerView() initRecyclerView()
initSearchView() initSearchView()
initLiveData() initLiveData()
viewModel.loadDbSearchBook()
} }
private fun showTitle() { private fun showTitle() {
@ -143,10 +141,11 @@ class ChangeSourceDialog() : BaseDialogFragment(R.layout.dialog_change_source),
} }
binding.toolBar.menu.applyTint(requireContext()) binding.toolBar.menu.applyTint(requireContext())
} }
viewModel.searchBooksLiveData.observe(viewLifecycleOwner) { launch {
viewModel.searchDataFlow
.collect {
adapter.setItems(it) adapter.setItems(it)
} }
launch {
appDb.bookSourceDao.flowGroupEnabled().collect { appDb.bookSourceDao.flowGroupEnabled().collect {
groups.clear() groups.clear()
it.map { group -> it.map { group ->
@ -165,7 +164,7 @@ class ChangeSourceDialog() : BaseDialogFragment(R.layout.dialog_change_source),
R.id.menu_check_author -> { R.id.menu_check_author -> {
AppConfig.changeSourceCheckAuthor = !item.isChecked AppConfig.changeSourceCheckAuthor = !item.isChecked
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
viewModel.loadDbSearchBook() viewModel.refresh()
} }
R.id.menu_load_toc -> { R.id.menu_load_toc -> {
putPrefBoolean(PreferKey.changeSourceLoadToc, !item.isChecked) putPrefBoolean(PreferKey.changeSourceLoadToc, !item.isChecked)
@ -186,7 +185,7 @@ class ChangeSourceDialog() : BaseDialogFragment(R.layout.dialog_change_source),
putPrefString("searchGroup", item.title.toString()) putPrefString("searchGroup", item.title.toString())
} }
viewModel.startOrStopSearch() viewModel.startOrStopSearch()
viewModel.loadDbSearchBook() viewModel.refresh()
} }
} }
} }

@ -17,12 +17,18 @@ import io.legado.app.help.coroutine.CompositeCoroutine
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
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO 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 splitties.init.appCtx
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.CopyOnWriteArraySet import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.math.min import kotlin.math.min
@ -30,17 +36,56 @@ import kotlin.math.min
class ChangeSourceViewModel(application: Application) : BaseViewModel(application) { class ChangeSourceViewModel(application: Application) : BaseViewModel(application) {
private val threadCount = AppConfig.threadCount private val threadCount = AppConfig.threadCount
private var searchPool: ExecutorCoroutineDispatcher? = null private var searchPool: ExecutorCoroutineDispatcher? = null
private var upAdapterJob: Job? = null
val searchStateData = MutableLiveData<Boolean>() val searchStateData = MutableLiveData<Boolean>()
val searchBooksLiveData = MutableLiveData<List<SearchBook>>()
var name: String = "" var name: String = ""
var author: String = "" var author: String = ""
private var tasks = CompositeCoroutine() private var tasks = CompositeCoroutine()
private var screenKey: String = "" private var screenKey: String = ""
private var bookSourceList = arrayListOf<BookSource>() private var bookSourceList = arrayListOf<BookSource>()
private val searchBooks = CopyOnWriteArraySet<SearchBook>() private val searchBooks = CopyOnWriteArrayList<SearchBook>()
private var postTime = 0L
private val searchGroup get() = appCtx.getPrefString("searchGroup") ?: "" 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 @Volatile
private var searchIndex = -1 private var searchIndex = -1
@ -62,56 +107,19 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
searchIndex = -1 searchIndex = -1
} }
fun loadDbSearchBook() { fun refresh() {
execute { getDbSearchBooks().let {
searchBooks.clear() searchBooks.clear()
upAdapter() searchBooks.addAll(it)
val sbs = if (AppConfig.changeSourceCheckAuthor) { searchCallback?.upAdapter()
appDb.searchBookDao.getChangeSourceSearch(name, author, searchGroup)
} else {
appDb.searchBookDao.getChangeSourceSearch(name, "", searchGroup)
}
searchBooks.addAll(sbs)
searchBooksLiveData.postValue(searchBooks.toList())
if (sbs.size <= 1) {
startSearch()
}
} }
} }
@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() { fun startSearch() {
execute { execute {
stopSearch() stopSearch()
appDb.searchBookDao.clear(name, author) appDb.searchBookDao.clear(name, author)
searchBooks.clear() searchBooks.clear()
upAdapter()
bookSourceList.clear() bookSourceList.clear()
if (searchGroup.isBlank()) { if (searchGroup.isBlank()) {
bookSourceList.addAll(appDb.bookSourceDao.allEnabled) bookSourceList.addAll(appDb.bookSourceDao.allEnabled)
@ -152,10 +160,10 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
if (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) { if (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) {
loadBookInfo(source, searchBook.toBook()) loadBookInfo(source, searchBook.toBook())
} else { } else {
searchFinish(searchBook) searchCallback?.searchSuccess(searchBook)
} }
} else { } else {
searchFinish(searchBook) searchCallback?.searchSuccess(searchBook)
} }
} }
} }
@ -190,7 +198,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
//从详情页里获取最新章节 //从详情页里获取最新章节
book.latestChapterTitle = it.latestChapterTitle book.latestChapterTitle = it.latestChapterTitle
val searchBook = book.toSearchBook() val searchBook = book.toSearchBook()
searchFinish(searchBook) searchCallback?.searchSuccess(searchBook)
} }
}.onError { }.onError {
Timber.e(it) Timber.e(it)
@ -202,26 +210,38 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
.onSuccess(IO) { chapters -> .onSuccess(IO) { chapters ->
book.latestChapterTitle = chapters.last().title book.latestChapterTitle = chapters.last().title
val searchBook: SearchBook = book.toSearchBook() val searchBook: SearchBook = book.toSearchBook()
searchFinish(searchBook) searchCallback?.searchSuccess(searchBook)
}.onError { }.onError {
Timber.e(it) 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?) { fun screen(key: String?) {
screenKey = key?.trim() ?: ""
execute { execute {
screenKey = key ?: "" getDbSearchBooks().let {
if (key.isNullOrEmpty()) {
loadDbSearchBook()
} else {
val items =
appDb.searchBookDao.getChangeSourceSearch(name, author, screenKey, searchGroup)
searchBooks.clear() searchBooks.clear()
searchBooks.addAll(items) searchBooks.addAll(it)
upAdapter() searchCallback?.upAdapter()
} }
} }
} }
@ -252,7 +272,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
appDb.bookSourceDao.update(source) appDb.bookSourceDao.update(source)
} }
searchBooks.remove(searchBook) searchBooks.remove(searchBook)
upAdapter() searchCallback?.upAdapter()
} }
} }
@ -265,7 +285,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
appDb.bookSourceDao.update(source) appDb.bookSourceDao.update(source)
updateSource(searchBook) updateSource(searchBook)
} }
upAdapter() searchCallback?.upAdapter()
} }
} }
@ -278,7 +298,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
appDb.bookSourceDao.update(source) appDb.bookSourceDao.update(source)
updateSource(searchBook) updateSource(searchBook)
} }
upAdapter() searchCallback?.upAdapter()
} }
} }
@ -294,11 +314,19 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
} }
} }
searchBooks.remove(searchBook) searchBooks.remove(searchBook)
upAdapter() searchCallback?.upAdapter()
} }
fun firstSourceOrNull(searchBook: SearchBook): SearchBook? { fun firstSourceOrNull(searchBook: SearchBook): SearchBook? {
return searchBooks.firstOrNull { it.bookUrl != searchBook.bookUrl } return searchBooks.firstOrNull { it.bookUrl != searchBook.bookUrl }
} }
interface SourceCallback {
fun searchSuccess(searchBook: SearchBook)
fun upAdapter()
}
} }
Loading…
Cancel
Save