From ee8cde3f264065ce42a8965b33a3d4989f4e8155 Mon Sep 17 00:00:00 2001 From: Rahul Kumar Patel Date: Mon, 5 Apr 2021 04:28:09 +0530 Subject: [PATCH] Add back search filters from v3 --- app/build.gradle | 2 +- .../main/java/com/aurora/store/data/Filter.kt | 29 +++ .../store/data/providers/FilterProvider.kt | 50 ++++++ .../java/com/aurora/store/util/Preferences.kt | 3 +- .../view/ui/onboarding/OnboardingActivity.kt | 2 + .../view/ui/search/SearchResultsActivity.kt | 140 +++++++++++---- .../store/view/ui/sheets/FilterSheet.kt | 138 ++++++++++++++ .../res/layout/activity_search_result.xml | 14 ++ app/src/main/res/layout/sheet_filter.xml | 168 ++++++++++++++++++ app/src/main/res/layout/sheet_tos.xml | 6 +- app/src/main/res/values/arrays.xml | 45 +++++ app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 3 + app/src/main/res/xml/preferences_filter.xml | 7 + 14 files changed, 575 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/com/aurora/store/data/Filter.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/FilterProvider.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/sheets/FilterSheet.kt create mode 100644 app/src/main/res/layout/sheet_filter.xml diff --git a/app/build.gradle b/app/build.gradle index dbaa7b893..8ed21a75f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -169,7 +169,7 @@ dependencies { implementation "com.github.topjohnwu.libsu:core:${versions.libsu}" //Love <3 - api("com.gitlab.AuroraOSS:gplayapi:efb14fa545") + api("com.gitlab.AuroraOSS:gplayapi:12bb682648") } Properties props = new Properties() diff --git a/app/src/main/java/com/aurora/store/data/Filter.kt b/app/src/main/java/com/aurora/store/data/Filter.kt new file mode 100644 index 000000000..60aee6def --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/Filter.kt @@ -0,0 +1,29 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data + +class Filter { + var appsWithAds = true + var appsWithIAP = true + var paidApps = true + var gsfDependentApps = true + var rating = 0.0f + var downloads = 0 +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/FilterProvider.kt b/app/src/main/java/com/aurora/store/data/providers/FilterProvider.kt new file mode 100644 index 000000000..27a26137c --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/FilterProvider.kt @@ -0,0 +1,50 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.store.data.Filter +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Preferences +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + +class FilterProvider private constructor(var context: Context) { + + companion object : SingletonHolder(::FilterProvider) { + const val PREFERENCE_FILTER = "PREFERENCE_FILTER" + } + + private var gson: Gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.TRANSIENT) + .create() + + fun getSavedFilter(): Filter { + var rawFilter = Preferences.getString(context, PREFERENCE_FILTER) + if (rawFilter.isEmpty()) + rawFilter = "{}" + return gson.fromJson(rawFilter, Filter::class.java) + } + + fun saveFilter(filter: Filter) { + Preferences.putString(context, PREFERENCE_FILTER, gson.toJson(filter)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/Preferences.kt b/app/src/main/java/com/aurora/store/util/Preferences.kt index 88588db6d..8c1f04e80 100644 --- a/app/src/main/java/com/aurora/store/util/Preferences.kt +++ b/app/src/main/java/com/aurora/store/util/Preferences.kt @@ -40,6 +40,7 @@ object Preferences { const val PREFERENCE_FILTER_GOOGLE = "PREFERENCE_FILTER_GOOGLE" const val PREFERENCE_FILTER_FDROID = "PREFERENCE_FILTER_FDROID" + const val PREFERENCE_FILTER_SEARCH = "PREFERENCE_FILTER_SEARCH" const val PREFERENCE_AUTO_INSTALL = "PREFERENCE_AUTO_INSTALL" const val PREFERENCE_AUTO_DELETE = "PREFERENCE_AUTO_DELETE" @@ -55,7 +56,7 @@ object Preferences { const val PREFERENCE_INSECURE_ANONYMOUS = "PREFERENCE_INSECURE_ANONYMOUS" - private fun getPrefs(context: Context): SharedPreferences { + fun getPrefs(context: Context): SharedPreferences { return PreferenceManager.getDefaultSharedPreferences(context) } diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt index ef7c4520e..1165d69b0 100644 --- a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt @@ -37,6 +37,7 @@ import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_ACTIVE import com.aurora.store.util.Preferences.PREFERENCE_DOWNLOAD_EXTERNAL import com.aurora.store.util.Preferences.PREFERENCE_FILTER_FDROID import com.aurora.store.util.Preferences.PREFERENCE_FILTER_GOOGLE +import com.aurora.store.util.Preferences.PREFERENCE_FILTER_SEARCH import com.aurora.store.util.Preferences.PREFERENCE_FOR_YOU import com.aurora.store.util.Preferences.PREFERENCE_INSECURE_ANONYMOUS import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID @@ -155,6 +156,7 @@ class OnboardingActivity : BaseActivity() { /*Filters*/ save(PREFERENCE_FILTER_FDROID, true) save(PREFERENCE_FILTER_GOOGLE, false) + save(PREFERENCE_FILTER_SEARCH, true) /*Downloader*/ save(PREFERENCE_DOWNLOAD_ACTIVE, 3) diff --git a/app/src/main/java/com/aurora/store/view/ui/search/SearchResultsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/search/SearchResultsActivity.kt index cfb30b5d0..ec8d08752 100644 --- a/app/src/main/java/com/aurora/store/view/ui/search/SearchResultsActivity.kt +++ b/app/src/main/java/com/aurora/store/view/ui/search/SearchResultsActivity.kt @@ -19,6 +19,8 @@ package com.aurora.store.view.ui.search +import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.os.Bundle import android.text.Editable import android.text.TextWatcher @@ -28,20 +30,27 @@ import android.view.inputmethod.EditorInfo import android.widget.TextView import androidx.lifecycle.ViewModelProvider import com.aurora.Constants -import com.aurora.gplayapi.data.models.SearchBundle -import com.aurora.store.databinding.ActivitySearchResultBinding import com.aurora.extensions.close import com.aurora.extensions.open +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.SearchBundle +import com.aurora.store.R +import com.aurora.store.data.Filter +import com.aurora.store.data.providers.FilterProvider +import com.aurora.store.databinding.ActivitySearchResultBinding +import com.aurora.store.util.Preferences import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener -import com.aurora.store.view.epoxy.views.app.AppListViewModel_ import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.app.AppListViewModel_ +import com.aurora.store.view.epoxy.views.app.NoAppViewModel_ import com.aurora.store.view.ui.commons.BaseActivity import com.aurora.store.view.ui.downloads.DownloadActivity +import com.aurora.store.view.ui.sheets.FilterSheet import com.aurora.store.viewmodel.search.SearchResultViewModel import com.google.android.material.textfield.TextInputEditText -class SearchResultsActivity : BaseActivity() { +class SearchResultsActivity : BaseActivity(), OnSharedPreferenceChangeListener { lateinit var B: ActivitySearchResultBinding lateinit var VM: SearchResultViewModel @@ -49,6 +58,9 @@ class SearchResultsActivity : BaseActivity() { lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener lateinit var searchView: TextInputEditText + lateinit var sharedPreferences: SharedPreferences + lateinit var filter: Filter + var query: String? = null var searchBundle: SearchBundle = SearchBundle() @@ -69,6 +81,10 @@ class SearchResultsActivity : BaseActivity() { B = ActivitySearchResultBinding.inflate(layoutInflater) VM = ViewModelProvider(this).get(SearchResultViewModel::class.java) + sharedPreferences = Preferences.getPrefs(this) + sharedPreferences.registerOnSharedPreferenceChangeListener(this) + filter = FilterProvider.with(this).getSavedFilter() + setContentView(B.root) VM.liveData.observe(this, { @@ -79,6 +95,7 @@ class SearchResultsActivity : BaseActivity() { attachToolbar() attachSearch() attachRecycler() + attachFilter() query = intent.getStringExtra(Constants.STRING_EXTRA) query?.let { @@ -86,6 +103,13 @@ class SearchResultsActivity : BaseActivity() { } } + override fun onDestroy() { + sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) + if (Preferences.getBoolean(this, Preferences.PREFERENCE_FILTER_SEARCH)) + FilterProvider.with(this).saveFilter(Filter()) + super.onDestroy() + } + private fun attachToolbar() { searchView = B.layoutViewToolbar.inputSearch @@ -97,6 +121,12 @@ class SearchResultsActivity : BaseActivity() { } } + private fun attachFilter() { + B.filterFab.setOnClickListener { + FilterSheet.newInstance().show(supportFragmentManager, "") + } + } + private fun attachRecycler() { endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { override fun onLoadMore(currentPage: Int) { @@ -107,27 +137,63 @@ class SearchResultsActivity : BaseActivity() { } private fun updateController(searchBundle: SearchBundle) { - B.recycler - .withModels { - setFilterDuplicates(true) - searchBundle.appList.forEach { app -> - add( - AppListViewModel_() - .id(app.id) - .app(app) - .click(View.OnClickListener { - openDetailsActivity(app) - }) - ) - } + val filteredAppList = filter(searchBundle.appList) - if (searchBundle.subBundles.isNotEmpty()) { + if (filteredAppList.isEmpty()) { + if (searchBundle.subBundles.isNotEmpty()) { + VM.next(searchBundle.subBundles) + B.recycler.withModels { + setFilterDuplicates(true) add( AppProgressViewModel_() .id("progress") ) } + } else { + B.recycler.adapter?.let { + /*Show empty search list if nothing found or no app matches filter criterion*/ + if (it.itemCount == 1 && searchBundle.subBundles.isEmpty()) { + B.recycler.withModels { + add( + NoAppViewModel_() + .id("no_app") + .message(getString(R.string.details_no_app_match)) + .icon(R.drawable.ic_round_search) + ) + } + } + } } + } else { + B.recycler + .withModels { + setFilterDuplicates(true) + + filteredAppList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click(View.OnClickListener { + openDetailsActivity(app) + }) + ) + } + + if (searchBundle.subBundles.isNotEmpty()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + + B.recycler.adapter?.let { + if (it.itemCount < 10) { + VM.next(searchBundle.subBundles) + } + } + } } private fun attachSearch() { @@ -137,12 +203,7 @@ class SearchResultsActivity : BaseActivity() { } override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - if (s.isNotEmpty()) { - /*val query = s.toString() - if (query.isNotEmpty()) { - VM.observeSearchResults(query) - }*/ - } + } override fun afterTextChanged(s: Editable) {} @@ -150,9 +211,9 @@ class SearchResultsActivity : BaseActivity() { searchView.setOnEditorActionListener { _: TextView?, actionId: Int, _: KeyEvent? -> if (actionId == EditorInfo.IME_ACTION_SEARCH) { - val query = searchView.text.toString() - if (query.isNotEmpty()) { - queryViewModel(query) + query = searchView.text.toString() + query?.let { + queryViewModel(it) return@setOnEditorActionListener true } } @@ -167,10 +228,29 @@ class SearchResultsActivity : BaseActivity() { } private fun queryViewModel(query: String) { + VM.observeSearchResults(query) endlessRecyclerOnScrollListener.resetPageCount() B.recycler.clear() - searchBundle.subBundles.clear() - searchBundle.appList.clear() - VM.observeSearchResults(query) + } + + private fun filter(appList: MutableList): List { + filter = FilterProvider.with(this).getSavedFilter() + return appList + .asSequence() + .filter { if (!filter.paidApps) it.isFree else true } + .filter { if (!filter.appsWithAds) !it.containsAds else true } + .filter { if (!filter.gsfDependentApps) it.dependencies.dependentPackages.isEmpty() else true } + .filter { if (filter.rating > 0) it.rating.average >= filter.rating else true } + .filter { if (filter.downloads > 0) it.installs >= filter.downloads else true } + .toList() + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + if (key == FilterProvider.PREFERENCE_FILTER) { + filter = FilterProvider.with(this).getSavedFilter() + query?.let { + queryViewModel(it) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/FilterSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/FilterSheet.kt new file mode 100644 index 000000000..0f6cb1cbb --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/FilterSheet.kt @@ -0,0 +1,138 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +package com.aurora.store.view.ui.sheets + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.Filter +import com.aurora.store.data.providers.FilterProvider +import com.aurora.store.databinding.SheetFilterBinding +import com.google.android.material.chip.Chip + +class FilterSheet : BaseBottomSheet() { + + private lateinit var B: SheetFilterBinding + private lateinit var filter: Filter + + companion object { + + const val TAG = "TOSSheet" + + @JvmStatic + fun newInstance(): FilterSheet { + return FilterSheet().apply { + arguments = Bundle().apply { + + } + } + } + } + + override fun onCreateContentView( + inflater: LayoutInflater, + container: ViewGroup, + savedInstanceState: Bundle? + ): View { + B = SheetFilterBinding.inflate(inflater, container, false) + filter = FilterProvider.with(requireContext()).getSavedFilter() + return B.root + } + + override fun onContentViewCreated(view: View, savedInstanceState: Bundle?) { + attachSingleChips() + attachMultipleChips() + attachActions() + } + + private fun attachActions() { + B.btnPositive.setOnClickListener { + FilterProvider.with(requireContext()).saveFilter(filter) + dismissAllowingStateLoss() + } + + B.btnNegative.setOnClickListener { + dismissAllowingStateLoss() + } + } + + private fun attachSingleChips() { + B.filterGfs.apply { + isChecked = filter.gsfDependentApps + setOnCheckedChangeListener { _, checked -> + isChecked = checked + filter.gsfDependentApps = checked + } + } + + B.filterPaid.apply { + isChecked = filter.paidApps + setOnCheckedChangeListener { _, checked -> + isChecked = checked + filter.paidApps = checked + } + } + + B.filterAds.apply { + isChecked = filter.appsWithAds + setOnCheckedChangeListener { _, checked -> + isChecked = checked + filter.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) + var i = 0 + + for (downloadLabel in downloadLabels) { + val chip = Chip(requireContext()) + chip.id = i + chip.text = downloadLabel + chip.isChecked = filter.downloads == downloadValues[i].toInt() + B.downloadChips.addView(chip) + i++ + } + + B.downloadChips.setOnCheckedChangeListener { _, id: Int -> + filter.downloads = downloadValues[id].toInt() + } + + i = 0 + for (ratingLabel in ratingLabels) { + val chip = Chip(requireContext()) + chip.id = i + chip.text = ratingLabel + chip.isChecked = filter.rating == ratingValues[i].toFloat() + B.ratingChips.addView(chip) + i++ + } + + B.ratingChips.setOnCheckedChangeListener { _, id: Int -> + filter.rating = ratingValues[id].toFloat() + } + } +} diff --git a/app/src/main/res/layout/activity_search_result.xml b/app/src/main/res/layout/activity_search_result.xml index 87c06a35b..f77df06bc 100644 --- a/app/src/main/res/layout/activity_search_result.xml +++ b/app/src/main/res/layout/activity_search_result.xml @@ -39,4 +39,18 @@ android:layout_below="@+id/layout_view_toolbar" app:itemSpacing="@dimen/margin_normal" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_filter.xml b/app/src/main/res/layout/sheet_filter.xml new file mode 100644 index 000000000..aecad42e1 --- /dev/null +++ b/app/src/main/res/layout/sheet_filter.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_tos.xml b/app/src/main/res/layout/sheet_tos.xml index 79091e32f..2554c1cf6 100644 --- a/app/src/main/res/layout/sheet_tos.xml +++ b/app/src/main/res/layout/sheet_tos.xml @@ -71,14 +71,14 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="end" - android:textAllCaps="false" - android:text="@string/onboarding_tos" /> + android:text="@string/onboarding_tos" + android:textAllCaps="false" /> diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 1d483749e..8631194fd 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -172,4 +172,49 @@ 2 + + 0 + 1.0 + 2.0 + 3.0 + 4.0 + 4.5 + 4.7 + + + + @string/action_filter_all + 1 + + 2 + + 3 + + 4 + + 4.5 + + 4.7 + + + + + 0 + 100 + 1000 + 10000 + 50000 + 100000 + 1000000 + 10000000 + 50000000 + 100000000 + + + + @string/action_filter_all + > 100 + > 1 K + > 10 K + > 50 K + > 100 K + > 1 M + > 10 M + > 50 M + > 100 M + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 86dac1fce..a60d7debc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -149,6 +149,7 @@ "More about app" "No ads" "No apps available" + "No app match found" "No Dependencies" "No previews available" "No permissions" @@ -268,6 +269,8 @@ "Filter F-Droid apps" "Removes all Google Apps from search results and category apps" "Filter Google apps" + "Search filters are reset on each search. Turn off to save search filters" + "Use non-persistent search filter" "Generate GSF ID locally on device" "Insecure anonymous session" "Apps are installed instantly after download completes" diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index cf4b8b764..c23d9e8d3 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -29,6 +29,7 @@ @color/colorScrimBlack @color/colorShimmer @color/colorStroke + @style/Widget.Aurora.Chip