compose: search: Bring back filters for search results

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2025-08-25 15:58:41 +08:00
parent 8ddba3d52c
commit 6cdb744099
15 changed files with 239 additions and 373 deletions

View 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)

View File

@@ -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))
}
}

View File

@@ -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
)

View File

@@ -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) {

View File

@@ -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
)

View File

@@ -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

View File

@@ -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))
}
}

View File

@@ -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())
}
}
}

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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()

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>