Global Search

This commit is contained in:
Syer10
2022-03-09 20:43:05 -05:00
parent 8b4bdff7ae
commit ad67895fa4
13 changed files with 593 additions and 11 deletions

View File

@@ -49,6 +49,7 @@
<string name="location_library">Library</string>
<string name="location_updates">Updates</string>
<string name="location_sources">Sources</string>
<string name="location_global_search">Global search</string>
<string name="location_extensions">Extensions</string>
<string name="location_downloads">Downloads</string>
<string name="location_settings">Settings</string>
@@ -91,6 +92,7 @@
<string name="reset_filters">Reset</string>
<string name="filter_source">Filter</string>
<string name="in_library">In library</string>
<string name="no_results_found">No results found</string>
<!-- Reader Menu -->
<string name="default_reader_mode">Default</string>

View File

@@ -26,6 +26,7 @@ import ca.gosyer.ui.settings.ThemesViewModel
import ca.gosyer.ui.sources.SourcesScreenViewModel
import ca.gosyer.ui.sources.browse.SourceScreenViewModel
import ca.gosyer.ui.sources.browse.filter.SourceFiltersViewModel
import ca.gosyer.ui.sources.globalsearch.GlobalSearchViewModel
import ca.gosyer.ui.sources.home.SourceHomeScreenViewModel
import ca.gosyer.ui.sources.settings.SourceSettingsScreenViewModel
import ca.gosyer.ui.updates.UpdatesScreenViewModel
@@ -56,6 +57,7 @@ actual class ViewModelFactoryImpl(
private val sourceFiltersFactory: (params: SourceFiltersViewModel.Params) -> SourceFiltersViewModel,
private val sourceSettingsFactory: (params: SourceSettingsScreenViewModel.Params) -> SourceSettingsScreenViewModel,
private val sourceHomeFactory: () -> SourceHomeScreenViewModel,
private val globalSearchFactory: (params: GlobalSearchViewModel.Params) -> GlobalSearchViewModel,
private val sourceFactory: (params: SourceScreenViewModel.Params) -> SourceScreenViewModel,
private val sourcesFactory: () -> SourcesScreenViewModel,
private val updatesFactory: () -> UpdatesScreenViewModel
@@ -84,6 +86,7 @@ actual class ViewModelFactoryImpl(
SourceFiltersViewModel::class -> sourceFiltersFactory(arg1 as SourceFiltersViewModel.Params)
SourceSettingsScreenViewModel::class -> sourceSettingsFactory(arg1 as SourceSettingsScreenViewModel.Params)
SourceHomeScreenViewModel::class -> sourceHomeFactory()
GlobalSearchViewModel::class -> globalSearchFactory(arg1 as GlobalSearchViewModel.Params)
SourceScreenViewModel::class -> sourceFactory(arg1 as SourceScreenViewModel.Params)
SourcesScreenViewModel::class -> sourcesFactory()
UpdatesScreenViewModel::class -> updatesFactory()

View File

@@ -27,6 +27,7 @@ import ca.gosyer.ui.settings.ThemesViewModel
import ca.gosyer.ui.sources.SourcesScreenViewModel
import ca.gosyer.ui.sources.browse.SourceScreenViewModel
import ca.gosyer.ui.sources.browse.filter.SourceFiltersViewModel
import ca.gosyer.ui.sources.globalsearch.GlobalSearchViewModel
import ca.gosyer.ui.sources.home.SourceHomeScreenViewModel
import ca.gosyer.ui.sources.settings.SourceSettingsScreenViewModel
import ca.gosyer.ui.updates.UpdatesScreenViewModel
@@ -58,6 +59,7 @@ actual class ViewModelFactoryImpl(
private val sourceFiltersFactory: (params: SourceFiltersViewModel.Params) -> SourceFiltersViewModel,
private val sourceSettingsFactory: (params: SourceSettingsScreenViewModel.Params) -> SourceSettingsScreenViewModel,
private val sourceHomeFactory: () -> SourceHomeScreenViewModel,
private val globalSearchFactory: (params: GlobalSearchViewModel.Params) -> GlobalSearchViewModel,
private val sourceFactory: (params: SourceScreenViewModel.Params) -> SourceScreenViewModel,
private val sourcesFactory: () -> SourcesScreenViewModel,
private val updatesFactory: () -> UpdatesScreenViewModel
@@ -87,6 +89,7 @@ actual class ViewModelFactoryImpl(
SourceFiltersViewModel::class -> sourceFiltersFactory(arg1 as SourceFiltersViewModel.Params)
SourceSettingsScreenViewModel::class -> sourceSettingsFactory(arg1 as SourceSettingsScreenViewModel.Params)
SourceHomeScreenViewModel::class -> sourceHomeFactory()
GlobalSearchViewModel::class -> globalSearchFactory(arg1 as GlobalSearchViewModel.Params)
SourceScreenViewModel::class -> sourceFactory(arg1 as SourceScreenViewModel.Params)
SourcesScreenViewModel::class -> sourcesFactory()
UpdatesScreenViewModel::class -> updatesFactory()

View File

@@ -20,14 +20,14 @@ import cafe.adriel.voyager.core.screen.ScreenKey
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
class SourceScreen(val source: Source) : Screen {
class SourceScreen(val source: Source, val initialQuery: String? = null) : Screen {
override val key: ScreenKey = source.id.toString()
@Composable
override fun Content() {
val sourceVM = viewModel {
instantiate<SourceScreenViewModel>(SourceScreenViewModel.Params(source))
instantiate<SourceScreenViewModel>(SourceScreenViewModel.Params(source, initialQuery))
}
val filterVM = viewModel {
instantiate<SourceFiltersViewModel>(SourceFiltersViewModel.Params(source.id))

View File

@@ -29,7 +29,8 @@ class SourceScreenViewModel(
private val sourceHandler: SourceInteractionHandler,
private val catalogPreferences: CatalogPreferences,
private val libraryPreferences: LibraryPreferences,
contextWrapper: ContextWrapper
contextWrapper: ContextWrapper,
initialQuery: String?
) : ViewModel(contextWrapper) {
@Inject constructor(
@@ -43,7 +44,8 @@ class SourceScreenViewModel(
sourceHandler,
catalogPreferences,
libraryPreferences,
contextWrapper
contextWrapper,
params.initialQuery
)
val displayMode = catalogPreferences.displayMode().stateIn(scope)
@@ -67,10 +69,10 @@ class SourceScreenViewModel(
private val _usingFilters = MutableStateFlow(false)
private val _sourceSearchQuery = MutableStateFlow<String?>(null)
private val _sourceSearchQuery = MutableStateFlow(initialQuery)
val sourceSearchQuery = _sourceSearchQuery.asStateFlow()
private val _query = MutableStateFlow<String?>(null)
private val _query = MutableStateFlow(sourceSearchQuery.value)
private val _pageNum = MutableStateFlow(1)
val pageNum = _pageNum.asStateFlow()
@@ -156,7 +158,7 @@ class SourceScreenViewModel(
catalogPreferences.displayMode().set(displayMode)
}
data class Params(val source: Source)
data class Params(val source: Source, val initialQuery: String?)
private companion object : CKLogger({})
}

View File

@@ -0,0 +1,53 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.ui.sources.globalsearch
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import ca.gosyer.ui.manga.MangaScreen
import ca.gosyer.ui.sources.browse.SourceScreen
import ca.gosyer.ui.sources.components.LocalSourcesNavigator
import ca.gosyer.ui.sources.globalsearch.components.GlobalSearchScreenContent
import ca.gosyer.uicore.vm.viewModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.ScreenKey
import cafe.adriel.voyager.core.screen.uniqueScreenKey
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
class GlobalSearchScreen(private val initialQuery: String) : Screen {
override val key: ScreenKey = uniqueScreenKey
@Composable
override fun Content() {
val vm = viewModel {
instantiate<GlobalSearchViewModel>(GlobalSearchViewModel.Params(initialQuery))
}
val sourcesNavigator = LocalSourcesNavigator.current
val navigator = LocalNavigator.currentOrThrow
GlobalSearchScreenContent(
sources = vm.sources.collectAsState().value,
results = vm.results,
displayMode = vm.displayMode.collectAsState().value,
query = vm.query.collectAsState().value,
setQuery = vm::setQuery,
submitSearch = vm::startSearch,
onSourceClick = {
if (sourcesNavigator != null) {
sourcesNavigator.select(it, vm.query.value)
} else {
navigator push SourceScreen(it, vm.query.value)
}
},
onMangaClick = {
navigator push MangaScreen(it.id)
}
)
}
}

View File

@@ -0,0 +1,149 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.ui.sources.globalsearch
import androidx.compose.runtime.snapshots.SnapshotStateMap
import ca.gosyer.core.logging.CKLogger
import ca.gosyer.data.catalog.CatalogPreferences
import ca.gosyer.data.models.MangaPage
import ca.gosyer.data.models.Source
import ca.gosyer.data.server.interactions.SourceInteractionHandler
import ca.gosyer.i18n.MR
import ca.gosyer.uicore.vm.ContextWrapper
import ca.gosyer.uicore.vm.ViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import me.tatarka.inject.annotations.Inject
class GlobalSearchViewModel @Inject constructor(
private val sourceHandler: SourceInteractionHandler,
catalogPreferences: CatalogPreferences,
contextWrapper: ContextWrapper,
params: Params
) : ViewModel(contextWrapper) {
private val _query = MutableStateFlow(params.initialQuery)
val query = _query.asStateFlow()
private val installedSources = MutableStateFlow(emptyList<Source>())
private val languages = catalogPreferences.languages().stateIn(scope)
val displayMode = catalogPreferences.displayMode().stateIn(scope)
private val _isLoading = MutableStateFlow(true)
val isLoading = _isLoading.asStateFlow()
val sources = combine(installedSources, languages) { installedSources, languages ->
installedSources.filter {
it.lang in languages || it.id == Source.LOCAL_SOURCE_ID
}
}.stateIn(scope, SharingStarted.Eagerly, emptyList())
private val search = MutableStateFlow(params.initialQuery)
val results = SnapshotStateMap<Long, Search>()
init {
getSources()
readySearch()
}
private fun getSources() {
sourceHandler.getSourceList()
.onEach { sources ->
installedSources.value = sources.sortedWith(
compareBy<Source, String>(String.CASE_INSENSITIVE_ORDER) { it.lang }
.thenBy(String.CASE_INSENSITIVE_ORDER) {
it.name
}
)
_isLoading.value = false
}
.catch {
info(it) { "Error getting sources" }
_isLoading.value = false
}
.launchIn(scope)
}
private val semaphore = Semaphore(5)
private fun readySearch() {
search
.combine(sources) { query, sources ->
query to sources
}
.mapLatest { (query, sources) ->
results.clear()
supervisorScope {
sources.map { source ->
async {
semaphore.withPermit {
sourceHandler
.getSearchResults(source, query, 1)
.map {
if (it.mangaList.isEmpty()) {
Search.Failure(MR.strings.no_results_found.toPlatformString())
} else {
Search.Success(it)
}
}
.catch {
info(it) { "Error getting search from ${source.displayName}" }
emit(Search.Failure(it))
}
.onEach {
results[source.id] = it
}
.collect()
}
}
}.awaitAll()
}
}
.catch {
info(it) { "Error getting sources" }
}
.flowOn(Dispatchers.IO)
.launchIn(scope)
}
fun setQuery(query: String) {
_query.value = query
}
fun startSearch(query: String) {
search.value = query
}
data class Params(val initialQuery: String)
sealed class Search {
object Searching : Search()
data class Success(val mangaPage: MangaPage) : Search()
data class Failure(val e: String?) : Search() {
constructor(e: Throwable) : this(e.message)
}
}
private companion object : CKLogger({})
}

View File

@@ -0,0 +1,73 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.ui.sources.globalsearch.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.times
import ca.gosyer.data.models.Manga
import ca.gosyer.ui.sources.browse.components.SourceMangaBadges
import ca.gosyer.uicore.components.mangaAspectRatio
import ca.gosyer.uicore.image.KamelImage
import io.kamel.image.lazyPainterResource
@Composable
fun GlobalSearchMangaComfortableGridItem(
modifier: Modifier,
manga: Manga,
inLibrary: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
val fontStyle = LocalTextStyle.current.merge(
TextStyle(letterSpacing = 0.sp, fontFamily = FontFamily.SansSerif, fontSize = 14.sp)
)
Box(
modifier = Modifier
.padding(4.dp)
.width((mangaAspectRatio * 200.dp))
.clip(MaterialTheme.shapes.medium) then modifier
) {
Column {
KamelImage(
cover,
contentDescription = manga.title,
modifier = Modifier
.height(200.dp)
.aspectRatio(mangaAspectRatio, true)
.clip(MaterialTheme.shapes.medium),
contentScale = ContentScale.Crop
)
Text(
text = manga.title,
style = fontStyle,
maxLines = 3,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 4.dp)
)
}
SourceMangaBadges(
inLibrary = inLibrary,
modifier = Modifier.padding(4.dp)
)
}
}

View File

@@ -0,0 +1,85 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.ui.sources.globalsearch.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import ca.gosyer.data.models.Manga
import ca.gosyer.ui.sources.browse.components.SourceMangaBadges
import ca.gosyer.uicore.components.mangaAspectRatio
import ca.gosyer.uicore.image.KamelImage
import io.kamel.image.lazyPainterResource
@Composable
fun GlobalSearchMangaCompactGridItem(
modifier: Modifier,
manga: Manga,
inLibrary: Boolean
) {
val cover = lazyPainterResource(manga, filterQuality = FilterQuality.Medium)
val fontStyle = LocalTextStyle.current.merge(
TextStyle(letterSpacing = 0.sp, fontFamily = FontFamily.SansSerif, fontSize = 14.sp)
)
Box(
modifier = Modifier.padding(4.dp)
.height(200.dp)
.aspectRatio(mangaAspectRatio, true)
.clip(MaterialTheme.shapes.medium) then modifier
) {
KamelImage(
cover,
manga.title,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
Box(modifier = Modifier.fillMaxSize().then(shadowGradient))
Text(
text = manga.title,
color = Color.White,
style = fontStyle,
maxLines = 2,
modifier = Modifier.align(Alignment.BottomStart).padding(8.dp)
)
SourceMangaBadges(
inLibrary = inLibrary,
modifier = Modifier.padding(4.dp)
)
}
}
private val shadowGradient = Modifier.drawWithCache {
val gradient = Brush.linearGradient(
0.75f to Color.Transparent,
1.0f to Color(0xAA000000),
start = Offset(0f, 0f),
end = Offset(0f, size.height)
)
onDrawBehind {
drawRect(gradient)
}
}

View File

@@ -0,0 +1,185 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.ui.sources.globalsearch.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Icon
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowForward
import androidx.compose.runtime.Composable
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import ca.gosyer.data.library.model.DisplayMode
import ca.gosyer.data.models.Manga
import ca.gosyer.data.models.Source
import ca.gosyer.i18n.MR
import ca.gosyer.ui.base.components.HorizontalScrollbar
import ca.gosyer.ui.base.components.VerticalScrollbar
import ca.gosyer.ui.base.components.localeToString
import ca.gosyer.ui.base.components.rememberScrollbarAdapter
import ca.gosyer.ui.base.navigation.Toolbar
import ca.gosyer.ui.sources.globalsearch.GlobalSearchViewModel.Search
import ca.gosyer.uicore.components.ErrorScreen
import ca.gosyer.uicore.resources.stringResource
@Composable
fun GlobalSearchScreenContent(
sources: List<Source>,
results: SnapshotStateMap<Long, Search>,
displayMode: DisplayMode,
query: String,
setQuery: (String) -> Unit,
submitSearch: (String) -> Unit,
onSourceClick: (Source) -> Unit,
onMangaClick: (Manga) -> Unit
) {
Scaffold(
topBar = {
Toolbar(
name = stringResource(MR.strings.location_global_search),
searchText = query,
search = setQuery,
searchSubmit = { submitSearch(query) }
)
}
) { padding ->
Box(Modifier.padding(padding)) {
val state = rememberLazyListState()
LazyColumn(state = state) {
val sourcesSuccess = sources.filter { results[it.id] is Search.Success }
val loadingSources = sources.filter { results[it.id] == null }
val failedSources = sources.filter { results[it.id] is Search.Failure }
items(sourcesSuccess) {
GlobalSearchItem(
source = it,
search = results[it.id] ?: Search.Searching,
displayMode = displayMode,
onSourceClick = onSourceClick,
onMangaClick = onMangaClick
)
}
items(loadingSources) {
GlobalSearchItem(
source = it,
search = results[it.id] ?: Search.Searching,
displayMode = displayMode,
onSourceClick = onSourceClick,
onMangaClick = onMangaClick
)
}
items(failedSources) {
GlobalSearchItem(
source = it,
search = results[it.id] ?: Search.Searching,
displayMode = displayMode,
onSourceClick = onSourceClick,
onMangaClick = onMangaClick
)
}
}
VerticalScrollbar(
modifier = Modifier.align(Alignment.CenterEnd)
.fillMaxHeight()
.padding(horizontal = 4.dp, vertical = 8.dp),
adapter = rememberScrollbarAdapter(state)
)
}
}
}
@Composable
fun GlobalSearchItem(
source: Source,
search: Search,
displayMode: DisplayMode,
onSourceClick: (Source) -> Unit,
onMangaClick: (Manga) -> Unit
) {
Column {
Row(
Modifier.fillMaxWidth()
.clickable { onSourceClick(source) }
.padding(vertical = 8.dp, horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
source.name,
maxLines = 1,
fontSize = 16.sp
)
Text(
localeToString(source.displayLang),
maxLines = 1,
fontSize = 12.sp
)
}
Icon(Icons.Rounded.ArrowForward, stringResource(MR.strings.action_search))
}
Spacer(Modifier.height(4.dp))
when (search) {
is Search.Failure -> Box(Modifier.fillMaxWidth().padding(vertical = 8.dp, horizontal = 16.dp), contentAlignment = Alignment.Center) {
ErrorScreen(search.e)
}
Search.Searching -> Box(Modifier.fillMaxWidth().padding(vertical = 8.dp, horizontal = 16.dp), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
is Search.Success -> Box(
Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp),
contentAlignment = Alignment.Center
) {
val state = rememberLazyListState()
LazyRow(Modifier.fillMaxSize(), state) {
items(search.mangaPage.mangaList) {
if (displayMode == DisplayMode.ComfortableGrid) {
GlobalSearchMangaComfortableGridItem(
Modifier.clickable { onMangaClick(it) },
it,
it.inLibrary
)
} else {
GlobalSearchMangaCompactGridItem(
Modifier.clickable { onMangaClick(it) },
it,
it.inLibrary
)
}
}
}
HorizontalScrollbar(
rememberScrollbarAdapter(state),
Modifier.align(Alignment.BottomCenter)
.fillMaxWidth()
)
}
}
}
}

View File

@@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import ca.gosyer.ui.sources.browse.SourceScreen
import ca.gosyer.ui.sources.components.LocalSourcesNavigator
import ca.gosyer.ui.sources.globalsearch.GlobalSearchScreen
import ca.gosyer.ui.sources.home.components.SourceHomeScreenContent
import ca.gosyer.uicore.vm.viewModel
import cafe.adriel.voyager.core.screen.Screen
@@ -37,7 +38,12 @@ class SourceHomeScreen : Screen {
sources = vm.sources.collectAsState().value,
languages = vm.languages.collectAsState().value,
sourceLanguages = vm.sourceLanguages.collectAsState().value,
setEnabledLanguages = vm::setEnabledLanguages
setEnabledLanguages = vm::setEnabledLanguages,
query = vm.query.collectAsState().value,
setQuery = vm::setQuery,
submitSearch = {
navigator push GlobalSearchScreen(it)
}
)
}
}

View File

@@ -46,6 +46,9 @@ class SourceHomeScreenViewModel @Inject constructor(
sources.map { it.lang }.toSet() - setOf(Source.LOCAL_SOURCE_LANG)
}.stateIn(scope, SharingStarted.Eagerly, emptySet())
private val _query = MutableStateFlow("")
val query = _query.asStateFlow()
init {
getSources()
}
@@ -73,5 +76,9 @@ class SourceHomeScreenViewModel @Inject constructor(
_languages.value = langs
}
fun setQuery(query: String) {
_query.value = query
}
private companion object : CKLogger({})
}

View File

@@ -64,13 +64,19 @@ fun SourceHomeScreenContent(
sources: List<Source>,
languages: Set<String>,
sourceLanguages: Set<String>,
setEnabledLanguages: (Set<String>) -> Unit
setEnabledLanguages: (Set<String>) -> Unit,
query: String,
setQuery: (String) -> Unit,
submitSearch: (String) -> Unit
) {
val languageDialogState = rememberMaterialDialogState()
Scaffold(
topBar = {
SourceHomeScreenToolbar(
languageDialogState::show
openEnabledLanguagesClick = languageDialogState::show,
query = query,
setQuery = setQuery,
submitSearch = submitSearch
)
}
) {
@@ -106,7 +112,10 @@ fun SourceHomeScreenContent(
@Composable
fun SourceHomeScreenToolbar(
openEnabledLanguagesClick: () -> Unit
openEnabledLanguagesClick: () -> Unit,
query: String,
setQuery: (String) -> Unit,
submitSearch: (String) -> Unit
) {
Toolbar(
stringResource(MR.strings.location_sources),
@@ -114,6 +123,11 @@ fun SourceHomeScreenToolbar(
getActionItems(
openEnabledLanguagesClick = openEnabledLanguagesClick
)
},
searchText = query,
search = setQuery,
searchSubmit = {
submitSearch(query)
}
)
}