|
|
|
@ -11,18 +11,19 @@ import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.Job
|
|
|
|
import kotlinx.coroutines.Job
|
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
|
|
import kotlinx.coroutines.flow.combine
|
|
|
|
import kotlinx.coroutines.flow.combine
|
|
|
|
import kotlinx.coroutines.flow.filterNotNull
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.R
|
|
|
|
import org.koitharu.kotatsu.R
|
|
|
|
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
|
|
|
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
|
|
|
import org.koitharu.kotatsu.list.ui.model.EmptyHint
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
|
|
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
|
|
|
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
|
|
|
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
|
|
|
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
|
|
|
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
|
|
|
import org.koitharu.kotatsu.parsers.model.Manga
|
|
|
|
import org.koitharu.kotatsu.parsers.model.Manga
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
|
|
|
import org.koitharu.kotatsu.scrobbling.domain.Scrobbler
|
|
|
|
import org.koitharu.kotatsu.scrobbling.domain.Scrobbler
|
|
|
|
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga
|
|
|
|
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.scrobbling.ui.selector.model.ScrobblerHint
|
|
|
|
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
|
|
|
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
|
|
|
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
|
|
|
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
|
|
|
import org.koitharu.kotatsu.utils.ext.requireValue
|
|
|
|
import org.koitharu.kotatsu.utils.ext.requireValue
|
|
|
|
|
|
|
|
|
|
|
|
class ScrobblingSelectorViewModel @AssistedInject constructor(
|
|
|
|
class ScrobblingSelectorViewModel @AssistedInject constructor(
|
|
|
|
@ -34,8 +35,9 @@ class ScrobblingSelectorViewModel @AssistedInject constructor(
|
|
|
|
|
|
|
|
|
|
|
|
val selectedScrobblerIndex = MutableLiveData(0)
|
|
|
|
val selectedScrobblerIndex = MutableLiveData(0)
|
|
|
|
|
|
|
|
|
|
|
|
private val scrobblerMangaList = MutableStateFlow<List<ScrobblerManga>?>(null)
|
|
|
|
private val scrobblerMangaList = MutableStateFlow<List<ScrobblerManga>>(emptyList())
|
|
|
|
private val hasNextPage = MutableStateFlow(false)
|
|
|
|
private val hasNextPage = MutableStateFlow(true)
|
|
|
|
|
|
|
|
private val listError = MutableStateFlow<Throwable?>(null)
|
|
|
|
private var loadingJob: Job? = null
|
|
|
|
private var loadingJob: Job? = null
|
|
|
|
private var doneJob: Job? = null
|
|
|
|
private var doneJob: Job? = null
|
|
|
|
private var initJob: Job? = null
|
|
|
|
private var initJob: Job? = null
|
|
|
|
@ -44,13 +46,24 @@ class ScrobblingSelectorViewModel @AssistedInject constructor(
|
|
|
|
get() = availableScrobblers[selectedScrobblerIndex.requireValue()]
|
|
|
|
get() = availableScrobblers[selectedScrobblerIndex.requireValue()]
|
|
|
|
|
|
|
|
|
|
|
|
val content: LiveData<List<ListModel>> = combine(
|
|
|
|
val content: LiveData<List<ListModel>> = combine(
|
|
|
|
scrobblerMangaList.filterNotNull(),
|
|
|
|
scrobblerMangaList,
|
|
|
|
|
|
|
|
listError,
|
|
|
|
hasNextPage,
|
|
|
|
hasNextPage,
|
|
|
|
) { list, isHasNextPage ->
|
|
|
|
) { list, error, isHasNextPage ->
|
|
|
|
|
|
|
|
if (list.isNotEmpty()) {
|
|
|
|
|
|
|
|
if (isHasNextPage) {
|
|
|
|
|
|
|
|
list + LoadingFooter
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
list
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
listOf(
|
|
|
|
when {
|
|
|
|
when {
|
|
|
|
list.isEmpty() -> listOf(emptyResultsHint())
|
|
|
|
error != null -> errorHint(error)
|
|
|
|
isHasNextPage -> list + LoadingFooter
|
|
|
|
isHasNextPage -> LoadingFooter
|
|
|
|
else -> list
|
|
|
|
else -> emptyResultsHint()
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
|
|
|
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
|
|
|
|
|
|
|
|
|
|
|
@ -59,7 +72,7 @@ class ScrobblingSelectorViewModel @AssistedInject constructor(
|
|
|
|
val onClose = SingleLiveEvent<Unit>()
|
|
|
|
val onClose = SingleLiveEvent<Unit>()
|
|
|
|
|
|
|
|
|
|
|
|
val isEmpty: Boolean
|
|
|
|
val isEmpty: Boolean
|
|
|
|
get() = scrobblerMangaList.value.isNullOrEmpty()
|
|
|
|
get() = scrobblerMangaList.value.isEmpty()
|
|
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
init {
|
|
|
|
initialize()
|
|
|
|
initialize()
|
|
|
|
@ -71,22 +84,39 @@ class ScrobblingSelectorViewModel @AssistedInject constructor(
|
|
|
|
loadList(append = false)
|
|
|
|
loadList(append = false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun loadList(append: Boolean) {
|
|
|
|
fun loadNextPage() {
|
|
|
|
if (loadingJob?.isActive == true) {
|
|
|
|
if (scrobblerMangaList.value.isNotEmpty() && hasNextPage.value) {
|
|
|
|
return
|
|
|
|
loadList(append = true)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (append && !hasNextPage.value) {
|
|
|
|
|
|
|
|
|
|
|
|
fun retry() {
|
|
|
|
|
|
|
|
loadingJob?.cancel()
|
|
|
|
|
|
|
|
hasNextPage.value = true
|
|
|
|
|
|
|
|
scrobblerMangaList.value = emptyList()
|
|
|
|
|
|
|
|
loadList(append = false)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun loadList(append: Boolean) {
|
|
|
|
|
|
|
|
if (loadingJob?.isActive == true) {
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
|
|
|
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
|
|
|
val offset = if (append) scrobblerMangaList.value?.size ?: 0 else 0
|
|
|
|
listError.value = null
|
|
|
|
val list = currentScrobbler.findManga(checkNotNull(searchQuery.value), offset)
|
|
|
|
val offset = if (append) scrobblerMangaList.value.size else 0
|
|
|
|
|
|
|
|
runCatchingCancellable {
|
|
|
|
|
|
|
|
currentScrobbler.findManga(checkNotNull(searchQuery.value), offset)
|
|
|
|
|
|
|
|
}.onSuccess { list ->
|
|
|
|
if (!append) {
|
|
|
|
if (!append) {
|
|
|
|
scrobblerMangaList.value = list
|
|
|
|
scrobblerMangaList.value = list
|
|
|
|
} else if (list.isNotEmpty()) {
|
|
|
|
} else if (list.isNotEmpty()) {
|
|
|
|
scrobblerMangaList.value = scrobblerMangaList.value?.plus(list) ?: list
|
|
|
|
scrobblerMangaList.value = scrobblerMangaList.value + list
|
|
|
|
}
|
|
|
|
}
|
|
|
|
hasNextPage.value = list.isNotEmpty()
|
|
|
|
hasNextPage.value = list.isNotEmpty()
|
|
|
|
|
|
|
|
}.onFailure { error ->
|
|
|
|
|
|
|
|
error.printStackTraceDebug()
|
|
|
|
|
|
|
|
listError.value = error
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -113,8 +143,8 @@ class ScrobblingSelectorViewModel @AssistedInject constructor(
|
|
|
|
private fun initialize() {
|
|
|
|
private fun initialize() {
|
|
|
|
initJob?.cancel()
|
|
|
|
initJob?.cancel()
|
|
|
|
loadingJob?.cancel()
|
|
|
|
loadingJob?.cancel()
|
|
|
|
hasNextPage.value = false
|
|
|
|
hasNextPage.value = true
|
|
|
|
scrobblerMangaList.value = null
|
|
|
|
scrobblerMangaList.value = emptyList()
|
|
|
|
initJob = launchJob(Dispatchers.Default) {
|
|
|
|
initJob = launchJob(Dispatchers.Default) {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
val info = currentScrobbler.getScrobblingInfoOrNull(manga.id)
|
|
|
|
val info = currentScrobbler.getScrobblingInfoOrNull(manga.id)
|
|
|
|
@ -127,13 +157,22 @@ class ScrobblingSelectorViewModel @AssistedInject constructor(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun emptyResultsHint() = EmptyHint(
|
|
|
|
private fun emptyResultsHint() = ScrobblerHint(
|
|
|
|
icon = R.drawable.ic_empty_history,
|
|
|
|
icon = R.drawable.ic_empty_history,
|
|
|
|
textPrimary = R.string.nothing_found,
|
|
|
|
textPrimary = R.string.nothing_found,
|
|
|
|
textSecondary = R.string.text_search_holder_secondary,
|
|
|
|
textSecondary = R.string.text_search_holder_secondary,
|
|
|
|
|
|
|
|
error = null,
|
|
|
|
actionStringRes = R.string.search,
|
|
|
|
actionStringRes = R.string.search,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun errorHint(e: Throwable) = ScrobblerHint(
|
|
|
|
|
|
|
|
icon = R.drawable.ic_error_large,
|
|
|
|
|
|
|
|
textPrimary = R.string.error_occurred,
|
|
|
|
|
|
|
|
error = e,
|
|
|
|
|
|
|
|
textSecondary = 0,
|
|
|
|
|
|
|
|
actionStringRes = R.string.try_again,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@AssistedFactory
|
|
|
|
@AssistedFactory
|
|
|
|
interface Factory {
|
|
|
|
interface Factory {
|
|
|
|
|
|
|
|
|
|
|
|
|