compose: search: Bring back filters for search results
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
11
app/src/main/java/com/aurora/extensions/App.kt
Normal file
11
app/src/main/java/com/aurora/extensions/App.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.extensions
|
||||
|
||||
import com.aurora.Constants.PACKAGE_NAME_GMS
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
|
||||
fun App.requiresGMS() = dependencies.dependentPackages.contains(PACKAGE_NAME_GMS)
|
||||
@@ -29,8 +29,8 @@ import coil3.compose.AsyncImage
|
||||
import coil3.compose.LocalAsyncImagePreviewHandler
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.crossfade
|
||||
import com.aurora.Constants.PACKAGE_NAME_GMS
|
||||
import com.aurora.extensions.bodyVerySmall
|
||||
import com.aurora.extensions.requiresGMS
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.compose.preview.AppPreviewProvider
|
||||
@@ -110,7 +110,7 @@ private fun buildExtras(app: App): List<String> {
|
||||
add(stringResource(R.string.details_no_ads))
|
||||
}
|
||||
|
||||
if (app.dependencies.dependentPackages.contains(PACKAGE_NAME_GMS)) {
|
||||
if (app.requiresGMS()) {
|
||||
add(stringResource(R.string.details_gsf_dependent))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,10 @@ import androidx.compose.ui.tooling.preview.PreviewScreenSizes
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil3.compose.LocalAsyncImagePreviewHandler
|
||||
import com.aurora.Constants.PACKAGE_NAME_GMS
|
||||
import com.aurora.Constants.SHARE_URL
|
||||
import com.aurora.extensions.appInfo
|
||||
import com.aurora.extensions.browse
|
||||
import com.aurora.extensions.requiresGMS
|
||||
import com.aurora.extensions.share
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
@@ -348,7 +348,7 @@ private fun ScreenContentApp(
|
||||
}
|
||||
|
||||
AppCompatibility(
|
||||
needsGms = app.dependencies.dependentPackages.contains(PACKAGE_NAME_GMS),
|
||||
needsGms = app.requiresGMS(),
|
||||
plexusScores = plexusScores
|
||||
)
|
||||
|
||||
|
||||
@@ -5,14 +5,23 @@
|
||||
|
||||
package com.aurora.store.compose.ui.search
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
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.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.material3.AppBarWithSearch
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExpandedDockedSearchBar
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
@@ -27,8 +36,11 @@ 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.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
@@ -36,6 +48,7 @@ import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
|
||||
@@ -57,6 +70,7 @@ 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.data.model.SearchFilter
|
||||
import com.aurora.store.viewmodel.search.SearchViewModel
|
||||
import kotlinx.coroutines.android.awaitFrame
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@@ -74,7 +88,8 @@ fun SearchScreen(onNavigateUp: () -> Unit, viewModel: SearchViewModel = hiltView
|
||||
results = results,
|
||||
onNavigateUp = onNavigateUp,
|
||||
onSearch = { query -> viewModel.search(query) },
|
||||
onFetchSuggestions = { query -> viewModel.fetchSuggestions(query) }
|
||||
onFetchSuggestions = { query -> viewModel.fetchSuggestions(query) },
|
||||
onFilter = { filter -> viewModel.filterResults(filter) }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -84,7 +99,8 @@ private fun ScreenContent(
|
||||
results: LazyPagingItems<App> = flowOf(PagingData.empty<App>()).collectAsLazyPagingItems(),
|
||||
onNavigateUp: () -> Unit = {},
|
||||
onFetchSuggestions: (String) -> Unit = {},
|
||||
onSearch: (String) -> Unit = {}
|
||||
onSearch: (String) -> Unit = {},
|
||||
onFilter: (filter: SearchFilter) -> Unit = {}
|
||||
) {
|
||||
val textFieldState = rememberTextFieldState()
|
||||
val searchBarState = rememberSearchBarState()
|
||||
@@ -104,12 +120,6 @@ private fun ScreenContent(
|
||||
.collectLatest { query -> onFetchSuggestions(query) }
|
||||
}
|
||||
|
||||
fun closeDetailPane() {
|
||||
coroutineScope.launch {
|
||||
scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, null)
|
||||
}
|
||||
}
|
||||
|
||||
fun showDetailPane(packageName: String) {
|
||||
coroutineScope.launch {
|
||||
scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, packageName)
|
||||
@@ -192,18 +202,27 @@ private fun ScreenContent(
|
||||
}
|
||||
|
||||
else -> {
|
||||
LazyColumn(
|
||||
Column(
|
||||
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) }
|
||||
)
|
||||
if (textFieldState.text.isNotBlank() && results.itemCount > 0) {
|
||||
FilterHeader(onFilter = onFilter)
|
||||
}
|
||||
|
||||
LazyColumn {
|
||||
items(
|
||||
count = results.itemCount,
|
||||
key = results.itemKey { it.id }
|
||||
) { index ->
|
||||
results[index]?.let { app ->
|
||||
AppListComposable(
|
||||
app = app,
|
||||
onClick = { showDetailPane(app.packageName) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -245,6 +264,129 @@ private fun ScreenContent(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FilterHeader(onFilter: (filter: SearchFilter) -> Unit) {
|
||||
var activeFilter by rememberSaveable { mutableStateOf(SearchFilter()) }
|
||||
|
||||
val filters = listOf(
|
||||
R.string.action_filter_rating,
|
||||
R.string.app_info_downloads,
|
||||
R.string.details_free,
|
||||
R.string.action_filter_no_ads,
|
||||
R.string.action_filter_no_gms
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun NonExpandableFilterChip(@StringRes filter: Int, isSelected: Boolean) {
|
||||
FilterChip(
|
||||
onClick = {
|
||||
activeFilter = when (filter) {
|
||||
R.string.details_free -> activeFilter.copy(isFree = !activeFilter.isFree)
|
||||
R.string.action_filter_no_ads -> activeFilter.copy(noAds = !activeFilter.noAds)
|
||||
else -> activeFilter.copy(noGMS = !activeFilter.noGMS)
|
||||
}
|
||||
onFilter(activeFilter)
|
||||
},
|
||||
label = { Text(text = stringResource(filter)) },
|
||||
selected = isSelected,
|
||||
leadingIcon = {
|
||||
if (isSelected) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_check),
|
||||
contentDescription = stringResource(filter)
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExpandableFilterChip(@StringRes filter: Int, isSelected: Boolean) {
|
||||
var isExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
Box {
|
||||
FilterChip(
|
||||
onClick = { isExpanded = !isExpanded },
|
||||
label = { Text(text = stringResource(filter)) },
|
||||
selected = isSelected,
|
||||
leadingIcon = {
|
||||
if (isSelected) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_check),
|
||||
contentDescription = stringResource(filter)
|
||||
)
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_arrow_drop_down),
|
||||
contentDescription = stringResource(filter)
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
DropdownMenu(expanded = isExpanded, onDismissRequest = { isExpanded = false }) {
|
||||
val downloadLabels = stringArrayResource(R.array.filterDownloadsLabels)
|
||||
val downloadValues = stringArrayResource(R.array.filterDownloadsValues)
|
||||
val ratingLabels = stringArrayResource(R.array.filterRatingLabels)
|
||||
val ratingValues = stringArrayResource(R.array.filterRatingValues)
|
||||
|
||||
val options = when (filter) {
|
||||
R.string.action_filter_rating -> ratingLabels.zip(ratingValues).toMap()
|
||||
R.string.app_info_downloads -> downloadLabels.zip(downloadValues).toMap()
|
||||
else -> emptyMap()
|
||||
}
|
||||
|
||||
options.forEach { (key, value) ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = key) },
|
||||
onClick = {
|
||||
activeFilter = when (filter) {
|
||||
R.string.action_filter_rating -> {
|
||||
activeFilter.copy(minRating = value.toFloat())
|
||||
}
|
||||
|
||||
else -> activeFilter.copy(minInstalls = value.toLong())
|
||||
}
|
||||
onFilter(activeFilter)
|
||||
isExpanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyRow(
|
||||
modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.padding_medium)),
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_normal))
|
||||
) {
|
||||
items(items = filters, key = { item -> item }) { filter ->
|
||||
val isSelected = when (filter) {
|
||||
R.string.action_filter_rating -> activeFilter.minRating > 0.0
|
||||
R.string.app_info_downloads -> activeFilter.minInstalls > 0
|
||||
R.string.details_free -> activeFilter.isFree
|
||||
R.string.action_filter_no_ads -> activeFilter.noAds
|
||||
R.string.action_filter_no_gms -> activeFilter.noGMS
|
||||
else -> false
|
||||
}
|
||||
|
||||
when (filter) {
|
||||
R.string.details_free,
|
||||
R.string.action_filter_no_ads,
|
||||
R.string.action_filter_no_gms -> {
|
||||
NonExpandableFilterChip(filter = filter, isSelected = isSelected)
|
||||
}
|
||||
|
||||
R.string.action_filter_rating,
|
||||
R.string.app_info_downloads -> {
|
||||
ExpandableFilterChip(filter = filter, isSelected = isSelected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewScreenSizes
|
||||
@Composable
|
||||
private fun SearchScreenPreview(@PreviewParameter(AppPreviewProvider::class) app: App) {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.data.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Filter(
|
||||
val appsWithAds: Boolean = true,
|
||||
val appsWithIAP: Boolean = true,
|
||||
val paidApps: Boolean = true,
|
||||
val gsfDependentApps: Boolean = true,
|
||||
val rating: Float = 0.0f,
|
||||
val downloads: Int = 0
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.data.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Filter for list with search results
|
||||
*/
|
||||
@Parcelize
|
||||
@Serializable
|
||||
data class SearchFilter(
|
||||
val noAds: Boolean = false,
|
||||
val isFree: Boolean = false,
|
||||
val noGMS: Boolean = false,
|
||||
val minRating: Float = 0F,
|
||||
val minInstalls: Long = 0
|
||||
) : Parcelable
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.data.providers
|
||||
|
||||
import android.content.Context
|
||||
import com.aurora.store.data.model.Filter
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.util.remove
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FilterProvider @Inject constructor(
|
||||
private val json: Json,
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
companion object {
|
||||
const val PREFERENCE_FILTER = "PREFERENCE_FILTER"
|
||||
}
|
||||
|
||||
init {
|
||||
// Clean any last saved filter
|
||||
context.remove(PREFERENCE_FILTER)
|
||||
}
|
||||
|
||||
fun getSavedFilter(): Filter {
|
||||
val rawFilter = Preferences.getString(context, PREFERENCE_FILTER)
|
||||
return json.decodeFromString<Filter>(rawFilter.ifEmpty { "{}" })
|
||||
}
|
||||
|
||||
fun saveFilter(filter: Filter) {
|
||||
Preferences.putString(context, PREFERENCE_FILTER, json.encodeToString(filter))
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.aurora.store.view.ui.sheets
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.Filter
|
||||
import com.aurora.store.databinding.SheetFilterBinding
|
||||
import com.aurora.store.viewmodel.sheets.FilterViewModel
|
||||
import com.google.android.material.chip.Chip
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class FilterSheet : BaseDialogSheet<SheetFilterBinding>() {
|
||||
|
||||
private val viewModel: FilterViewModel by viewModels()
|
||||
|
||||
private lateinit var filter: Filter
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
filter = viewModel.filterProvider.getSavedFilter()
|
||||
|
||||
attachSingleChips()
|
||||
attachMultipleChips()
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
viewModel.filterProvider.saveFilter(filter)
|
||||
}
|
||||
|
||||
private fun attachSingleChips() {
|
||||
binding.filterGfs.apply {
|
||||
isChecked = filter.gsfDependentApps
|
||||
setOnCheckedChangeListener { _, checked ->
|
||||
isChecked = checked
|
||||
filter = filter.copy(gsfDependentApps = checked)
|
||||
}
|
||||
}
|
||||
|
||||
binding.filterPaid.apply {
|
||||
isChecked = filter.paidApps
|
||||
setOnCheckedChangeListener { _, checked ->
|
||||
isChecked = checked
|
||||
filter = filter.copy(paidApps = checked)
|
||||
}
|
||||
}
|
||||
|
||||
binding.filterAds.apply {
|
||||
isChecked = filter.appsWithAds
|
||||
setOnCheckedChangeListener { _, checked ->
|
||||
isChecked = checked
|
||||
filter = filter.copy(appsWithAds = checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun attachMultipleChips() {
|
||||
val downloadLabels = resources.getStringArray(R.array.filterDownloadsLabels)
|
||||
val downloadValues = resources.getStringArray(R.array.filterDownloadsValues)
|
||||
val ratingLabels = resources.getStringArray(R.array.filterRatingLabels)
|
||||
val ratingValues = resources.getStringArray(R.array.filterRatingValues)
|
||||
|
||||
downloadLabels.forEachIndexed { index, value ->
|
||||
val chip = Chip(requireContext())
|
||||
chip.id = index
|
||||
chip.text = value
|
||||
chip.textColors
|
||||
chip.isChecked = filter.downloads == downloadValues[index].toInt()
|
||||
binding.downloadChips.addView(chip)
|
||||
}
|
||||
|
||||
binding.downloadChips.setOnCheckedStateChangeListener { _, checkedIds ->
|
||||
filter = filter.copy(downloads = downloadValues[checkedIds[0]].toInt())
|
||||
}
|
||||
|
||||
ratingLabels.forEachIndexed { index, value ->
|
||||
val chip = Chip(requireContext())
|
||||
chip.id = index
|
||||
chip.text = value
|
||||
chip.isChecked = filter.rating == ratingValues[index].toFloat()
|
||||
binding.ratingChips.addView(chip)
|
||||
}
|
||||
|
||||
binding.ratingChips.setOnCheckedStateChangeListener { _, checkedIds ->
|
||||
filter = filter.copy(rating = ratingValues[checkedIds[0]].toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.Constants
|
||||
import com.aurora.Constants.PACKAGE_NAME_GMS
|
||||
import com.aurora.extensions.requiresGMS
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.Review
|
||||
import com.aurora.gplayapi.data.models.details.TestingProgramStatus
|
||||
@@ -162,9 +162,7 @@ class AppDetailsViewModel @Inject constructor(
|
||||
fetchDataSafetyReport(packageName)
|
||||
fetchSuggestions()
|
||||
fetchExodusPrivacyReport(packageName)
|
||||
if (app.value!!.dependencies.dependentPackages.contains(PACKAGE_NAME_GMS)) {
|
||||
fetchPlexusReport(packageName)
|
||||
}
|
||||
if (app.value!!.requiresGMS()) fetchPlexusReport(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
/*
|
||||
* Aurora Store
|
||||
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2021 Rahul Kumar Patel <whyorean@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.viewmodel.search
|
||||
@@ -24,21 +11,27 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.paging.filter
|
||||
import com.aurora.extensions.requiresGMS
|
||||
import com.aurora.gplayapi.SearchSuggestEntry
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.gplayapi.helpers.SearchHelper
|
||||
import com.aurora.gplayapi.helpers.contracts.SearchContract
|
||||
import com.aurora.gplayapi.helpers.web.WebSearchHelper
|
||||
import com.aurora.store.data.model.SearchFilter
|
||||
import com.aurora.store.data.paging.GenericPagingSource.Companion.createPager
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -57,8 +50,24 @@ class SearchViewModel @Inject constructor(
|
||||
private val _suggestions = MutableStateFlow<List<SearchSuggestEntry>>(emptyList())
|
||||
val suggestions = _suggestions.asStateFlow()
|
||||
|
||||
private val _filter = MutableStateFlow(SearchFilter())
|
||||
private val _apps = MutableStateFlow<PagingData<App>>(PagingData.empty())
|
||||
val apps = _apps.asStateFlow()
|
||||
val apps = combine(_filter, _apps) { filter, pagingData ->
|
||||
pagingData.filter { app ->
|
||||
when {
|
||||
filter.noAds && app.containsAds -> false
|
||||
filter.isFree && !app.isFree -> false
|
||||
filter.noGMS && app.requiresGMS() -> false
|
||||
app.rating.average < filter.minRating -> false
|
||||
app.installs < filter.minInstalls -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), PagingData.empty())
|
||||
|
||||
fun filterResults(filter: SearchFilter) {
|
||||
_filter.value = filter
|
||||
}
|
||||
|
||||
fun search(query: String) {
|
||||
var nextBundleUrl: String? = null
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The Calyx Institute
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.aurora.store.viewmodel.sheets
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.aurora.store.data.providers.FilterProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class FilterViewModel @Inject constructor(val filterProvider: FilterProvider) : ViewModel()
|
||||
14
app/src/main/res/drawable/ic_arrow_drop_down.xml
Normal file
14
app/src/main/res/drawable/ic_arrow_drop_down.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M480,600L280,400L680,400L480,600Z"/>
|
||||
</vector>
|
||||
@@ -1,113 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Aurora Store
|
||||
~ Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
|
||||
~
|
||||
~ 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/>.
|
||||
~
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/drag_handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@drawable/divider"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/padding_normal"
|
||||
android:showDividers="middle">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_filter_rating"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.SubTitle" />
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/rating_chips"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:chipSpacingHorizontal="@dimen/margin_small"
|
||||
app:chipSpacingVertical="@dimen/margin_xsmall"
|
||||
app:selectionRequired="true"
|
||||
app:singleSelection="true" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_filter_downloads"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.SubTitle" />
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/download_chips"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:chipSpacingHorizontal="@dimen/margin_small"
|
||||
app:chipSpacingVertical="@dimen/margin_xsmall"
|
||||
app:selectionRequired="true"
|
||||
app:singleSelection="true" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_filter_misc"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.SubTitle" />
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/filter_gfs"
|
||||
style="@style/Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_filter_gsf_dependent_apps" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/filter_paid"
|
||||
style="@style/Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_filter_paid_apps" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/filter_ads"
|
||||
style="@style/Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_filter_apps_with_ads" />
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -134,7 +134,6 @@
|
||||
<item>3.0</item>
|
||||
<item>4.0</item>
|
||||
<item>4.5</item>
|
||||
<item>4.7</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="filterRatingLabels" translatable="false">
|
||||
@@ -144,15 +143,10 @@
|
||||
<item> 3★ + </item>
|
||||
<item> 4★ + </item>
|
||||
<item> 4.5★ + </item>
|
||||
<item> 4.7★ + </item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="filterDownloadsValues" translatable="false">
|
||||
<item>0</item>
|
||||
<item>100</item>
|
||||
<item>1000</item>
|
||||
<item>10000</item>
|
||||
<item>50000</item>
|
||||
<item>100000</item>
|
||||
<item>1000000</item>
|
||||
<item>10000000</item>
|
||||
@@ -162,10 +156,6 @@
|
||||
|
||||
<string-array name="filterDownloadsLabels" translatable="false">
|
||||
<item>@string/action_filter_all</item>
|
||||
<item> 100 + </item>
|
||||
<item> 1K + </item>
|
||||
<item> 10K + </item>
|
||||
<item> 50K + </item>
|
||||
<item> 100K + </item>
|
||||
<item> 1M + </item>
|
||||
<item> 10M + </item>
|
||||
|
||||
@@ -534,4 +534,6 @@
|
||||
|
||||
<!-- SearchScreen -->
|
||||
<string name="select_app_for_details">Select an app for more details</string>
|
||||
<string name="action_filter_no_ads">No advertisements</string>
|
||||
<string name="action_filter_no_gms">No play services</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user