compose: Initial migration of searching logic to compose

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2025-08-13 15:50:36 +08:00
parent 458b984a06
commit 62e81da258
18 changed files with 327 additions and 100 deletions

View File

@@ -3,7 +3,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package com.aurora.store.compose.composables.app package com.aurora.store.compose.composables
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
@@ -33,7 +33,7 @@ import com.aurora.store.R
* @param onAction Callback when action button is clicked * @param onAction Callback when action button is clicked
*/ */
@Composable @Composable
fun NoAppComposable( fun ErrorComposable(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@DrawableRes icon: Int, @DrawableRes icon: Int,
@StringRes message: Int, @StringRes message: Int,
@@ -69,8 +69,8 @@ fun NoAppComposable(
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
private fun NoAppComposablePreview() { private fun ErrorComposablePreview() {
NoAppComposable( ErrorComposable(
icon = R.drawable.ic_updates, icon = R.drawable.ic_updates,
message = R.string.details_no_updates, message = R.string.details_no_updates,
actionMessage = R.string.check_updates actionMessage = R.string.check_updates

View File

@@ -3,7 +3,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package com.aurora.store.compose.composables.app package com.aurora.store.compose.composables
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -24,7 +24,7 @@ import com.aurora.store.R
*/ */
@Composable @Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
fun AppProgressComposable(modifier: Modifier = Modifier) { fun ProgressComposable(modifier: Modifier = Modifier) {
Box( Box(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
@@ -39,6 +39,6 @@ fun AppProgressComposable(modifier: Modifier = Modifier) {
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
private fun AppProgressComposablePreview() { private fun ProgressComposablePreview() {
AppProgressComposable() ProgressComposable()
} }

View File

@@ -41,13 +41,13 @@ import com.aurora.store.R
fun SearchSuggestionComposable( fun SearchSuggestionComposable(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
searchSuggestEntry: SearchSuggestEntry, searchSuggestEntry: SearchSuggestEntry,
onClick: () -> Unit = {}, onClick: (query: String) -> Unit = {},
onAction: () -> Unit = {} onAction: (query: String) -> Unit = {}
) { ) {
Row( Row(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onClick) .clickable(onClick = { onClick(searchSuggestEntry.title) })
.padding(dimensionResource(R.dimen.padding_medium)), .padding(dimensionResource(R.dimen.padding_medium)),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
@@ -55,7 +55,7 @@ fun SearchSuggestionComposable(
Row( Row(
modifier = Modifier modifier = Modifier
.weight(1F) .weight(1F)
.clickable(onClick = onClick), .clickable(onClick = { onClick(searchSuggestEntry.title) }),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium)) horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium))
) { ) {
AsyncImage( AsyncImage(
@@ -81,7 +81,7 @@ fun SearchSuggestionComposable(
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
} }
IconButton(onClick = onAction) { IconButton(onClick = { onAction(searchSuggestEntry.title) }) {
Icon( Icon(
painter = painterResource(R.drawable.ic_search_append), painter = painterResource(R.drawable.ic_search_append),
contentDescription = null contentDescription = null

View File

@@ -42,7 +42,10 @@ fun TopAppBarComposable(
navigationIcon = { navigationIcon = {
if (onNavigateUp != null) { if (onNavigateUp != null) {
IconButton(onClick = onNavigateUp) { IconButton(onClick = onNavigateUp) {
Icon(painter = navigationIcon, contentDescription = null) Icon(
painter = navigationIcon,
contentDescription = stringResource(R.string.action_back)
)
} }
} }
}, },

View File

@@ -14,6 +14,7 @@ import androidx.navigation.toRoute
import com.aurora.store.compose.ui.commons.BlacklistScreen import com.aurora.store.compose.ui.commons.BlacklistScreen
import com.aurora.store.compose.ui.details.AppDetailsScreen import com.aurora.store.compose.ui.details.AppDetailsScreen
import com.aurora.store.compose.ui.dev.DevProfileScreen import com.aurora.store.compose.ui.dev.DevProfileScreen
import com.aurora.store.compose.ui.search.SearchScreen
/** /**
* Navigation graph for compose screens * Navigation graph for compose screens
@@ -37,6 +38,10 @@ fun NavGraph(navHostController: NavHostController, startDestination: Screen) {
BlacklistScreen(onNavigateUp = { onNavigateUp() }) BlacklistScreen(onNavigateUp = { onNavigateUp() })
} }
composable<Screen.Search> {
SearchScreen(onNavigateUp = { onNavigateUp() })
}
composable<Screen.AppDetails> { backstackEntry -> composable<Screen.AppDetails> { backstackEntry ->
val appDetails = backstackEntry.toRoute<Screen.AppDetails>() val appDetails = backstackEntry.toRoute<Screen.AppDetails>()
AppDetailsScreen( AppDetailsScreen(

View File

@@ -37,6 +37,9 @@ sealed class Screen(
@Serializable @Serializable
data class AppDetails(val packageName: String) : Screen() data class AppDetails(val packageName: String) : Screen()
@Serializable
data object Search : Screen()
/** /**
* Child screen of [AppDetails]; Avoid navigating to this screen directly. * Child screen of [AppDetails]; Avoid navigating to this screen directly.
*/ */

View File

@@ -53,8 +53,8 @@ import com.aurora.store.R
import com.aurora.store.compose.composables.HeaderComposable import com.aurora.store.compose.composables.HeaderComposable
import com.aurora.store.compose.composables.TopAppBarComposable import com.aurora.store.compose.composables.TopAppBarComposable
import com.aurora.store.compose.composables.app.AppListComposable import com.aurora.store.compose.composables.app.AppListComposable
import com.aurora.store.compose.composables.app.AppProgressComposable import com.aurora.store.compose.composables.ProgressComposable
import com.aurora.store.compose.composables.app.NoAppComposable import com.aurora.store.compose.composables.ErrorComposable
import com.aurora.store.compose.preview.AppPreviewProvider import com.aurora.store.compose.preview.AppPreviewProvider
import com.aurora.store.compose.preview.coilPreviewProvider import com.aurora.store.compose.preview.coilPreviewProvider
import com.aurora.store.compose.menu.AppDetailsMenu import com.aurora.store.compose.menu.AppDetailsMenu
@@ -101,7 +101,7 @@ fun AppDetailsScreen(
val plexusScores by viewModel.plexusScores.collectAsStateWithLifecycle() val plexusScores by viewModel.plexusScores.collectAsStateWithLifecycle()
val suggestions by viewModel.suggestions.collectAsStateWithLifecycle() val suggestions by viewModel.suggestions.collectAsStateWithLifecycle()
LaunchedEffect(key1 = Unit) { viewModel.fetchAppDetails(packageName) } LaunchedEffect(key1 = packageName) { viewModel.fetchAppDetails(packageName) }
with(app) { with(app) {
when { when {
@@ -155,7 +155,7 @@ private fun ScreenContentLoading(onNavigateUp: () -> Unit = {}) {
Scaffold( Scaffold(
topBar = { TopAppBarComposable(onNavigateUp = onNavigateUp) } topBar = { TopAppBarComposable(onNavigateUp = onNavigateUp) }
) { paddingValues -> ) { paddingValues ->
AppProgressComposable(modifier = Modifier.padding(paddingValues)) ProgressComposable(modifier = Modifier.padding(paddingValues))
} }
} }
@@ -167,7 +167,7 @@ private fun ScreenContentError(onNavigateUp: () -> Unit = {}) {
Scaffold( Scaffold(
topBar = { TopAppBarComposable(onNavigateUp = onNavigateUp) } topBar = { TopAppBarComposable(onNavigateUp = onNavigateUp) }
) { paddingValues -> ) { paddingValues ->
NoAppComposable( ErrorComposable(
modifier = Modifier.padding(paddingValues), modifier = Modifier.padding(paddingValues),
icon = R.drawable.ic_apps_outage, icon = R.drawable.ic_apps_outage,
message = R.string.toast_app_unavailable message = R.string.toast_app_unavailable

View File

@@ -29,12 +29,14 @@ import coil3.compose.LocalAsyncImagePreviewHandler
import com.aurora.extensions.adaptiveNavigationIcon import com.aurora.extensions.adaptiveNavigationIcon
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.compose.composables.ErrorComposable
import com.aurora.store.compose.composables.ProgressComposable
import com.aurora.store.compose.composables.TopAppBarComposable import com.aurora.store.compose.composables.TopAppBarComposable
import com.aurora.store.compose.composables.app.AppListComposable import com.aurora.store.compose.composables.app.AppListComposable
import com.aurora.store.compose.preview.AppPreviewProvider import com.aurora.store.compose.preview.AppPreviewProvider
import com.aurora.store.compose.preview.coilPreviewProvider import com.aurora.store.compose.preview.coilPreviewProvider
import com.aurora.store.viewmodel.details.DevProfileViewModel import com.aurora.store.viewmodel.details.DevProfileViewModel
import com.aurora.store.viewmodel.search.SearchResultViewModel import com.aurora.store.viewmodel.search.SearchViewModel
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlin.random.Random import kotlin.random.Random
@@ -61,7 +63,7 @@ fun DevProfileScreen(
publisherId: String, publisherId: String,
onNavigateUp: () -> Unit, onNavigateUp: () -> Unit,
onNavigateToAppDetails: (packageName: String) -> Unit, onNavigateToAppDetails: (packageName: String) -> Unit,
viewModel: SearchResultViewModel = hiltViewModel() viewModel: SearchViewModel = hiltViewModel()
) { ) {
val apps = viewModel.apps.collectAsLazyPagingItems() val apps = viewModel.apps.collectAsLazyPagingItems()
@@ -93,9 +95,15 @@ private fun ScreenContent(
} }
) { paddingValues -> ) { paddingValues ->
when (apps.loadState.refresh) { when (apps.loadState.refresh) {
is LoadState.Loading -> {} is LoadState.Loading -> ProgressComposable()
is LoadState.Error -> {} is LoadState.Error -> {
ErrorComposable(
modifier = Modifier.padding(paddingValues),
icon = R.drawable.ic_disclaimer,
message = R.string.error
)
}
else -> { else -> {
LazyColumn( LazyColumn(

View File

@@ -0,0 +1,248 @@
/*
* SPDX-FileCopyrightText: 2025 The Calyx Institute
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.aurora.store.compose.ui.search
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.DockedSearchBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.LoadState
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import coil3.annotation.ExperimentalCoilApi
import coil3.compose.LocalAsyncImagePreviewHandler
import com.aurora.gplayapi.SearchSuggestEntry
import com.aurora.gplayapi.data.models.App
import com.aurora.store.R
import com.aurora.store.compose.composables.ErrorComposable
import com.aurora.store.compose.composables.ProgressComposable
import com.aurora.store.compose.composables.SearchSuggestionComposable
import com.aurora.store.compose.composables.app.AppListComposable
import com.aurora.store.compose.preview.AppPreviewProvider
import com.aurora.store.compose.preview.coilPreviewProvider
import com.aurora.store.compose.ui.details.AppDetailsScreen
import com.aurora.store.viewmodel.search.SearchViewModel
import kotlinx.coroutines.android.awaitFrame
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlin.random.Random
@Composable
fun SearchScreen(onNavigateUp: () -> Unit, viewModel: SearchViewModel = hiltViewModel()) {
val suggestions by viewModel.suggestions.collectAsStateWithLifecycle()
val results = viewModel.apps.collectAsLazyPagingItems()
ScreenContent(
suggestions = suggestions,
results = results,
onNavigateUp = onNavigateUp,
onSearch = { query -> viewModel.newSearch(query) },
onFetchSuggestions = { query -> viewModel.fetchSuggestions(query) }
)
}
@Composable
@OptIn(ExperimentalMaterial3AdaptiveApi::class, ExperimentalMaterial3Api::class)
private fun ScreenContent(
suggestions: List<SearchSuggestEntry> = emptyList(),
results: LazyPagingItems<App> = flowOf(PagingData.empty<App>()).collectAsLazyPagingItems(),
onNavigateUp: () -> Unit = {},
onFetchSuggestions: (String) -> Unit = {},
onSearch: (String) -> Unit = {}
) {
var currentQuery by rememberSaveable { mutableStateOf("") }
var isExpanded by rememberSaveable { mutableStateOf(true) }
val focusRequester = remember { FocusRequester() }
val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<String>()
val coroutineScope = rememberCoroutineScope()
LaunchedEffect(focusRequester) {
awaitFrame()
focusRequester.requestFocus()
}
fun closeDetailPane() {
coroutineScope.launch {
scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, null)
}
}
fun showDetailPane(packageName: String) {
coroutineScope.launch {
scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, packageName)
}
}
fun onRequestSuggestions(query: String) {
currentQuery = query.trim()
onFetchSuggestions(query.trim())
}
fun onRequestSearch(query: String) {
currentQuery = query.trim()
isExpanded = false
onSearch(query.trim())
}
@Composable
fun SearchBar() {
DockedSearchBar(
expanded = isExpanded,
onExpandedChange = { expanded -> isExpanded = expanded },
inputField = {
SearchBarDefaults.InputField(
modifier = Modifier.focusRequester(focusRequester),
query = currentQuery,
onQueryChange = { query -> onRequestSuggestions(query) },
onSearch = { query -> onRequestSearch(query) },
expanded = isExpanded,
onExpandedChange = { expanded -> isExpanded = expanded },
leadingIcon = {
IconButton(onClick = onNavigateUp) {
Icon(
painter = painterResource(R.drawable.ic_arrow_back),
contentDescription = stringResource(R.string.action_back)
)
}
},
trailingIcon = {
if (currentQuery.isNotBlank() && isExpanded) {
IconButton(onClick = { currentQuery = "" }) {
Icon(
painter = painterResource(R.drawable.ic_cancel),
contentDescription = stringResource(R.string.action_clear)
)
}
}
},
placeholder = { Text(text = stringResource(R.string.search_hint)) }
)
}
) {
suggestions.forEach { suggestion ->
SearchSuggestionComposable(
searchSuggestEntry = suggestion,
onClick = { query -> onRequestSearch(query) }
)
}
}
}
@Composable
fun ListPane() {
Scaffold(
topBar = {
TopAppBar(title = { SearchBar() })
}
) { paddingValues ->
when (results.loadState.refresh) {
is LoadState.Loading -> ProgressComposable()
is LoadState.Error -> {
ErrorComposable(
modifier = Modifier.padding(paddingValues),
icon = R.drawable.ic_disclaimer,
message = R.string.error
)
}
else -> {
LazyColumn(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.padding(vertical = dimensionResource(R.dimen.padding_medium))
) {
items(count = results.itemCount, key = results.itemKey { it.id }) { index ->
results[index]?.let { app ->
AppListComposable(
app = app,
onClick = { showDetailPane(app.packageName) }
)
}
}
}
}
}
}
}
@Composable
fun DetailPane() {
with(scaffoldNavigator.currentDestination?.contentKey) {
when {
this != null -> {
AppDetailsScreen(
packageName = this,
onNavigateUp = ::closeDetailPane,
onNavigateToAppDetails = { packageName -> showDetailPane(packageName) }
)
}
else -> {
if (currentQuery.isNotBlank() && results.itemCount > 0) {
ErrorComposable(
icon = R.drawable.ic_round_search,
message = R.string.select_app_for_details
)
}
}
}
}
}
NavigableListDetailPaneScaffold(
navigator = scaffoldNavigator,
listPane = { AnimatedPane { ListPane() } },
detailPane = { AnimatedPane { DetailPane() } }
)
}
@PreviewScreenSizes
@Composable
@OptIn(ExperimentalCoilApi::class)
private fun SearchScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) {
val apps = List(10) { app.copy(id = Random.nextInt()) }
val results = flowOf(PagingData.from(apps)).collectAsLazyPagingItems()
CompositionLocalProvider(LocalAsyncImagePreviewHandler provides coilPreviewProvider) {
ScreenContent(results = results)
}
}

View File

@@ -31,8 +31,10 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import com.aurora.extensions.navigate
import com.aurora.store.MobileNavigationDirections import com.aurora.store.MobileNavigationDirections
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.compose.navigation.Screen
import com.aurora.store.databinding.FragmentAppsGamesBinding import com.aurora.store.databinding.FragmentAppsGamesBinding
import com.aurora.store.util.Preferences import com.aurora.store.util.Preferences
import com.aurora.store.view.ui.commons.BaseFragment import com.aurora.store.view.ui.commons.BaseFragment
@@ -114,7 +116,7 @@ class AppsContainerFragment : BaseFragment<FragmentAppsGamesBinding>() {
}.attach() }.attach()
binding.searchFab.setOnClickListener { binding.searchFab.setOnClickListener {
findNavController().navigate(R.id.searchSuggestionFragment) requireContext().navigate(Screen.Search)
} }
} }

View File

@@ -41,14 +41,14 @@ import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
import com.aurora.store.view.epoxy.controller.GenericCarouselController import com.aurora.store.view.epoxy.controller.GenericCarouselController
import com.aurora.store.view.epoxy.controller.SearchCarouselController import com.aurora.store.view.epoxy.controller.SearchCarouselController
import com.aurora.store.view.ui.commons.BaseFragment import com.aurora.store.view.ui.commons.BaseFragment
import com.aurora.store.viewmodel.search.SearchResultViewModel import com.aurora.store.viewmodel.search.SearchViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
class SearchResultsFragment : BaseFragment<FragmentSearchResultBinding>(), class SearchResultsFragment : BaseFragment<FragmentSearchResultBinding>(),
GenericCarouselController.Callbacks { GenericCarouselController.Callbacks {
private val viewModel: SearchResultViewModel by activityViewModels() private val viewModel: SearchViewModel by activityViewModels()
private val controller = SearchCarouselController(this) private val controller = SearchCarouselController(this)
private var query: String private var query: String

View File

@@ -146,6 +146,10 @@ class AppDetailsViewModel @Inject constructor(
fun fetchAppDetails(packageName: String) { fun fetchAppDetails(packageName: String) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
// Reset current details to show latest ones
_app.value = App("")
_suggestions.value = emptyList()
_app.value = appDetailsHelper.getAppByPackageName(packageName).copy( _app.value = appDetailsHelper.getAppByPackageName(packageName).copy(
isInstalled = PackageUtil.isInstalled(context, packageName) isInstalled = PackageUtil.isInstalled(context, packageName)
) )

View File

@@ -25,6 +25,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import com.aurora.gplayapi.SearchSuggestEntry
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.StreamCluster
@@ -48,13 +49,13 @@ import kotlinx.coroutines.sync.withLock
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class SearchResultViewModel @Inject constructor( class SearchViewModel @Inject constructor(
private val authProvider: AuthProvider, private val authProvider: AuthProvider,
private val searchHelper: SearchHelper, private val searchHelper: SearchHelper,
private val webSearchHelper: WebSearchHelper private val webSearchHelper: WebSearchHelper
) : ViewModel() { ) : ViewModel() {
private val TAG = SearchResultViewModel::class.java.simpleName private val TAG = SearchViewModel::class.java.simpleName
val liveData: MutableLiveData<ViewState> = MutableLiveData() val liveData: MutableLiveData<ViewState> = MutableLiveData()
@@ -63,6 +64,9 @@ class SearchResultViewModel @Inject constructor(
private val contract: SearchContract private val contract: SearchContract
get() = if (authProvider.isAnonymous) webSearchHelper else searchHelper get() = if (authProvider.isAnonymous) webSearchHelper else searchHelper
private val _suggestions = MutableStateFlow<List<SearchSuggestEntry>>(emptyList())
val suggestions = _suggestions.asStateFlow()
private val _apps = MutableStateFlow<PagingData<App>>(PagingData.empty()) private val _apps = MutableStateFlow<PagingData<App>>(PagingData.empty())
val apps = _apps.asStateFlow() val apps = _apps.asStateFlow()
@@ -119,6 +123,12 @@ class SearchResultViewModel @Inject constructor(
.launchIn(viewModelScope) .launchIn(viewModelScope)
} }
fun fetchSuggestions(query: String) {
viewModelScope.launch(Dispatchers.IO) {
_suggestions.value = contract.searchSuggestions(query)
}
}
@Synchronized @Synchronized
fun search(query: String) { fun search(query: String) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {

View File

@@ -1,26 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- <!--
~ Aurora Store ~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com> ~ SPDX-License-Identifier: Apache-2.0
~
~ Aurora Store is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 2 of the License, or
~ (at your option) any later version.
~
~ Aurora Store is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
~
--> -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="?android:attr/colorControlNormal"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path

View File

@@ -1,26 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- <!--
~ Aurora Store ~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com> ~ SPDX-License-Identifier: Apache-2.0
~
~ Aurora Store is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 2 of the License, or
~ (at your option) any later version.
~
~ Aurora Store is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
~
--> -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="960" android:viewportWidth="960"
android:viewportHeight="960"> android:viewportHeight="960">
<path <path

View File

@@ -1,26 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- <!--
~ Aurora Store ~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com> ~ SPDX-License-Identifier: Apache-2.0
~
~ Aurora Store is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 2 of the License, or
~ (at your option) any later version.
~
~ Aurora Store is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
~
--> -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="960" android:viewportWidth="960"
android:viewportHeight="960"> android:viewportHeight="960">
<path <path

View File

@@ -1,28 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- <!--
~ Aurora Store ~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com> ~ SPDX-License-Identifier: Apache-2.0
~
~ Aurora Store is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 2 of the License, or
~ (at your option) any later version.
~
~ Aurora Store is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
~
--> -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#FF000000" android:fillColor="@android:color/white"
android:pathData="M15.5,14h-0.79l-0.28,-0.27c1.2,-1.4 1.82,-3.31 1.48,-5.34 -0.47,-2.78 -2.79,-5 -5.59,-5.34 -4.23,-0.52 -7.79,3.04 -7.27,7.27 0.34,2.8 2.56,5.12 5.34,5.59 2.03,0.34 3.94,-0.28 5.34,-1.48l0.27,0.28v0.79l4.25,4.25c0.41,0.41 1.08,0.41 1.49,0 0.41,-0.41 0.41,-1.08 0,-1.49L15.5,14zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" /> android:pathData="M15.5,14h-0.79l-0.28,-0.27c1.2,-1.4 1.82,-3.31 1.48,-5.34 -0.47,-2.78 -2.79,-5 -5.59,-5.34 -4.23,-0.52 -7.79,3.04 -7.27,7.27 0.34,2.8 2.56,5.12 5.34,5.59 2.03,0.34 3.94,-0.28 5.34,-1.48l0.27,0.28v0.79l4.25,4.25c0.41,0.41 1.08,0.41 1.49,0 0.41,-0.41 0.41,-1.08 0,-1.49L15.5,14zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
</vector> </vector>

View File

@@ -531,4 +531,7 @@
<!-- DownloadWorker --> <!-- DownloadWorker -->
<string name="verification_failed">Failed to verify downloaded files</string> <string name="verification_failed">Failed to verify downloaded files</string>
<!-- SearchScreen -->
<string name="select_app_for_details">Select an app for more details</string>
</resources> </resources>