@ -23,6 +23,8 @@ import kotlinx.coroutines.plus
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.LocalMangaSource
import org.koitharu.kotatsu.core.model.UnknownMangaSource
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.BaseViewModel
@ -31,13 +33,17 @@ import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import javax.inject.Inject
@ -45,16 +51,19 @@ private const val MAX_PARALLELISM = 4
private const val MIN _HAS _MORE _ITEMS = 8
@HiltViewModel
class Multi SearchViewModel @Inject constructor (
class SearchViewModel @Inject constructor (
savedStateHandle : SavedStateHandle ,
private val mangaListMapper : MangaListMapper ,
private val mangaRepositoryFactory : MangaRepository . Factory ,
private val downloadScheduler : DownloadWorker . Scheduler ,
private val sourcesRepository : MangaSourcesRepository ,
private val historyRepository : HistoryRepository ,
private val localMangaRepository : LocalMangaRepository ,
private val favouritesRepository : FavouritesRepository ,
) : BaseViewModel ( ) {
val onDownloadStarted = MutableEventFlow < Unit > ( )
val query = savedStateHandle . get < String > ( Multi SearchActivity. EXTRA _QUERY ) . orEmpty ( )
val query = savedStateHandle . get < String > ( SearchActivity. EXTRA _QUERY ) . orEmpty ( )
private val retryCounter = MutableStateFlow ( 0 )
private val listData = retryCounter . flatMapLatest {
@ -108,7 +117,10 @@ class MultiSearchViewModel @Inject constructor(
}
@CheckResult
private fun searchImpl ( q : String ) : Flow < List < MultiSearchListModel > > = channelFlow {
private fun searchImpl ( q : String ) : Flow < List < SearchResultsListModel > > = channelFlow {
searchHistory ( q ) ?. let { send ( it ) }
searchFavorites ( q ) ?. let { send ( it ) }
searchLocal ( q ) ?. let { send ( it ) }
val sources = sourcesRepository . getEnabledSources ( )
if ( sources . isEmpty ( ) ) {
return @channelFlow
@ -132,12 +144,12 @@ class MultiSearchViewModel @Inject constructor(
if ( list . isEmpty ( ) ) {
null
} else {
Multi SearchListModel( source , list . size > MIN _HAS _MORE _ITEMS , list , null )
SearchResults ListModel( 0 , source , list . size > MIN _HAS _MORE _ITEMS , list , null )
}
} ,
onFailure = { error ->
error . printStackTraceDebug ( )
Multi SearchListModel( source , true , emptyList ( ) , error )
SearchResults ListModel( 0 , source , true , emptyList ( ) , error )
} ,
)
if ( item != null ) {
@ -146,7 +158,94 @@ class MultiSearchViewModel @Inject constructor(
}
}
} . joinAll ( )
} . runningFold < Multi SearchListModel, List < Multi SearchListModel> ? > ( null ) { list , item -> list . orEmpty ( ) + item }
} . runningFold < SearchResults ListModel, List < SearchResults ListModel> ? > ( null ) { list , item -> list . orEmpty ( ) + item }
. filterNotNull ( )
. onEmpty { emit ( emptyList ( ) ) }
private suspend fun searchHistory ( q : String ) : SearchResultsListModel ? {
return runCatchingCancellable {
historyRepository . search ( q , Int . MAX _VALUE )
} . fold (
onSuccess = { result ->
if ( result . isNotEmpty ( ) ) {
SearchResultsListModel (
titleResId = R . string . history ,
source = UnknownMangaSource ,
hasMore = false ,
list = mangaListMapper . toListModelList ( manga = result , mode = ListMode . GRID ) ,
error = null ,
)
} else {
null
}
} ,
onFailure = { error ->
SearchResultsListModel (
titleResId = R . string . history ,
source = UnknownMangaSource ,
hasMore = false ,
list = emptyList ( ) ,
error = error ,
)
} ,
)
}
private suspend fun searchFavorites ( q : String ) : SearchResultsListModel ? {
return runCatchingCancellable {
favouritesRepository . search ( q , Int . MAX _VALUE )
} . fold (
onSuccess = { result ->
if ( result . isNotEmpty ( ) ) {
SearchResultsListModel (
titleResId = R . string . favourites ,
source = UnknownMangaSource ,
hasMore = false ,
list = mangaListMapper . toListModelList ( manga = result , mode = ListMode . GRID ) ,
error = null ,
)
} else {
null
}
} ,
onFailure = { error ->
SearchResultsListModel (
titleResId = R . string . favourites ,
source = UnknownMangaSource ,
hasMore = false ,
list = emptyList ( ) ,
error = error ,
)
} ,
)
}
private suspend fun searchLocal ( q : String ) : SearchResultsListModel ? {
return runCatchingCancellable {
localMangaRepository . getList ( 0 , SortOrder . RELEVANCE , MangaListFilter ( query = q ) )
} . fold (
onSuccess = { result ->
if ( result . isNotEmpty ( ) ) {
SearchResultsListModel (
titleResId = 0 ,
source = LocalMangaSource ,
hasMore = result . size > MIN _HAS _MORE _ITEMS ,
list = mangaListMapper . toListModelList ( manga = result , mode = ListMode . GRID ) ,
error = null ,
)
} else {
null
}
} ,
onFailure = { error ->
SearchResultsListModel (
titleResId = 0 ,
source = LocalMangaSource ,
hasMore = true ,
list = emptyList ( ) ,
error = error ,
)
} ,
)
}
}