From 1d33e7ab49a239d49f79a2c049699df5806a61a5 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 6 May 2020 22:02:07 +0200 Subject: [PATCH 1/3] Allow choosing which types of search suggestions to show local, remote, both, none Replacing the old on-off setting --- .../fragments/list/search/SearchFragment.java | 145 +++++++++--------- .../fragments/list/search/SuggestionItem.java | 16 ++ .../list/search/SuggestionListAdapter.java | 16 +- .../settings/ContentSettingsFragment.java | 7 +- .../newpipe/settings/NewPipeSettings.java | 26 ++++ .../newpipe/settings/SettingMigrations.java | 27 +++- app/src/main/res/values/settings_keys.xml | 10 ++ app/src/main/res/values/strings.xml | 4 +- app/src/main/res/xml/content_settings.xml | 6 +- 9 files changed, 159 insertions(+), 98 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 478cf94f3..8b596e3a8 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -55,8 +55,8 @@ import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQu import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.ktx.AnimationType; -import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.history.HistoryRecordManager; +import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; @@ -65,16 +65,19 @@ import org.schabi.newpipe.util.ServiceHelper; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.Set; import java.util.concurrent.TimeUnit; import icepick.State; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; @@ -143,7 +146,8 @@ public class SearchFragment extends BaseListFragment menuItemToFilterName = null; private StreamingService service; private Page nextPage; - private boolean isSuggestionsEnabled = true; + private boolean showLocalSuggestions = true; + private boolean showRemoteSuggestions = true; private Disposable searchDisposable; private Disposable suggestionDisposable; @@ -194,26 +198,14 @@ public class SearchFragment extends BaseListFragment> getLocalSuggestionsObservable( + final String query, final int similarQueryLimit) { + return historyRecordManager + .getRelatedSearches(query, similarQueryLimit, 25) + .toObservable() + .map(searchHistoryEntries -> { + final Set result = new HashSet<>(); // remove duplicates + for (final SearchHistoryEntry entry : searchHistoryEntries) { + result.add(new SuggestionItem(true, entry.getSearch())); + } + return new ArrayList<>(result); + }); + } + + private Observable> getRemoteSuggestionsObservable(final String query) { + return ExtractorHelper + .suggestionsFor(serviceId, query) + .toObservable() + .map(strings -> { + final List result = new ArrayList<>(); + for (final String entry : strings) { + result.add(new SuggestionItem(false, entry)); + } + return result; + }); + } + private void initSuggestionObserver() { if (DEBUG) { Log.d(TAG, "initSuggestionObserver() called"); @@ -753,68 +774,42 @@ public class SearchFragment extends BaseListFragment isSuggestionsEnabled) + .startWithItem(searchString == null ? "" : searchString) .switchMap(query -> { - final Flowable> flowable = historyRecordManager - .getRelatedSearches(query, 3, 25); - final Observable> local = flowable.toObservable() - .map(searchHistoryEntries -> { - final List result = new ArrayList<>(); - for (final SearchHistoryEntry entry : searchHistoryEntries) { - result.add(new SuggestionItem(true, entry.getSearch())); - } - return result; - }); + // Only show remote suggestions if they are enabled in settings and + // the query length is at least THRESHOLD_NETWORK_SUGGESTION + final boolean shallShowRemoteSuggestionsNow = showRemoteSuggestions + && query.length() >= THRESHOLD_NETWORK_SUGGESTION; - if (query.length() < THRESHOLD_NETWORK_SUGGESTION) { - // Only pass through if the query length - // is equal or greater than THRESHOLD_NETWORK_SUGGESTION - return local.materialize(); + if (showLocalSuggestions && shallShowRemoteSuggestionsNow) { + return Observable.zip(getLocalSuggestionsObservable(query, 3), + getRemoteSuggestionsObservable(query), + (local, remote) -> { + remote.removeIf(remoteItem -> local.stream().anyMatch( + localItem -> localItem.equals(remoteItem))); + local.addAll(remote); + return local; + }) + .materialize(); + } else if (showLocalSuggestions) { + return getLocalSuggestionsObservable(query, 25) + .materialize(); + } else if (shallShowRemoteSuggestionsNow) { + return getRemoteSuggestionsObservable(query) + .materialize(); + } else { + return Single.fromCallable(Collections::emptyList) + .toObservable() + .materialize(); } - - final Observable> network = ExtractorHelper - .suggestionsFor(serviceId, query) - .onErrorReturn(throwable -> { - if (!ExceptionUtils.isNetworkRelated(throwable)) { - showSnackBarError(new ErrorInfo(throwable, - UserAction.GET_SUGGESTIONS, searchString, serviceId)); - } - return new ArrayList<>(); - }) - .toObservable() - .map(strings -> { - final List result = new ArrayList<>(); - for (final String entry : strings) { - result.add(new SuggestionItem(false, entry)); - } - return result; - }); - - return Observable.zip(local, network, (localResult, networkResult) -> { - final List result = new ArrayList<>(); - if (localResult.size() > 0) { - result.addAll(localResult); - } - - // Remove duplicates - networkResult.removeIf(networkItem -> - localResult.stream().anyMatch(localItem -> - localItem.query.equals(networkItem.query))); - - if (networkResult.size() > 0) { - result.addAll(networkResult); - } - return result; - }).materialize(); }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(listNotification -> { if (listNotification.isOnNext()) { - handleSuggestions(listNotification.getValue()); + if (listNotification.getValue() != null) { + handleSuggestions(listNotification.getValue()); + } } else if (listNotification.isOnError()) { showError(new ErrorInfo(listNotification.getError(), UserAction.GET_SUGGESTIONS, searchString, serviceId)); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java index 5aa927ed3..83f68dbb5 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.fragments.list.search; +import androidx.annotation.NonNull; + public class SuggestionItem { final boolean fromHistory; public final String query; @@ -9,6 +11,20 @@ public class SuggestionItem { this.query = query; } + @Override + public boolean equals(final Object o) { + if (o instanceof SuggestionItem) { + return query.equals(((SuggestionItem) o).query); + } + return false; + } + + @Override + public int hashCode() { + return query.hashCode(); + } + + @NonNull @Override public String toString() { return "[" + fromHistory + "→" + query + "]"; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java index 952316796..3cfcfd470 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java @@ -19,7 +19,6 @@ public class SuggestionListAdapter private final ArrayList items = new ArrayList<>(); private final Context context; private OnSuggestionItemSelected listener; - private boolean showSuggestionHistory = true; public SuggestionListAdapter(final Context context) { this.context = context; @@ -27,16 +26,7 @@ public class SuggestionListAdapter public void setItems(final List items) { this.items.clear(); - if (showSuggestionHistory) { - this.items.addAll(items); - } else { - // remove history items if history is disabled - for (final SuggestionItem item : items) { - if (!item.fromHistory) { - this.items.add(item); - } - } - } + this.items.addAll(items); notifyDataSetChanged(); } @@ -44,10 +34,6 @@ public class SuggestionListAdapter this.listener = listener; } - public void setShowSuggestionHistory(final boolean v) { - showSuggestionHistory = v; - } - @Override public SuggestionItemHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { return new SuggestionItemHolder(LayoutInflater.from(context) diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index bb27a80eb..1a86cf38b 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -92,8 +92,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment { .getPreferredLocalization(requireContext()); initialSelectedContentCountry = org.schabi.newpipe.util.Localization .getPreferredContentCountry(requireContext()); - initialLanguage = PreferenceManager - .getDefaultSharedPreferences(requireContext()).getString("app_language_key", "en"); + initialLanguage = defaultPreferences.getString(getString(R.string.app_language_key), "en"); final Preference clearCookiePref = requirePreference(R.string.clear_cookie_key); clearCookiePref.setOnPreferenceClickListener(preference -> { @@ -147,8 +146,8 @@ public class ContentSettingsFragment extends BasePreferenceFragment { .getPreferredLocalization(requireContext()); final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization .getPreferredContentCountry(requireContext()); - final String selectedLanguage = PreferenceManager - .getDefaultSharedPreferences(requireContext()).getString("app_language_key", "en"); + final String selectedLanguage = + defaultPreferences.getString(getString(R.string.app_language_key), "en"); if (!selectedLocalization.equals(initialSelectedLocalization) || !selectedContentCountry.equals(initialSelectedContentCountry) diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java index 33f00ec1a..de39bf755 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java +++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java @@ -6,6 +6,7 @@ import android.os.Build; import android.os.Environment; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; @@ -124,4 +125,29 @@ public final class NewPipeSettings { return prefs.getBoolean(key, true); } + + private static boolean showSearchSuggestions(final Context context, + final SharedPreferences sharedPreferences, + @StringRes final int key) { + final Set enabledSearchSuggestions = sharedPreferences.getStringSet( + context.getString(R.string.show_search_suggestions_key), null); + + if (enabledSearchSuggestions == null) { + return true; // defaults to true + } else { + return enabledSearchSuggestions.contains(context.getString(key)); + } + } + + public static boolean showLocalSearchSuggestions(final Context context, + final SharedPreferences sharedPreferences) { + return showSearchSuggestions(context, sharedPreferences, + R.string.show_local_search_suggestions_key); + } + + public static boolean showRemoteSearchSuggestions(final Context context, + final SharedPreferences sharedPreferences) { + return showSearchSuggestions(context, sharedPreferences, + R.string.show_remote_search_suggestions_key); + } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java index 2d5fedec0..f96c24a40 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java @@ -13,6 +13,10 @@ import org.schabi.newpipe.error.ErrorInfo; import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.util.DeviceUtils; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + import static org.schabi.newpipe.MainActivity.DEBUG; public final class SettingMigrations { @@ -72,6 +76,26 @@ public final class SettingMigrations { } }; + public static final Migration MIGRATION_3_4 = new Migration(3, 4) { + @Override + protected void migrate(final Context context) { + // Pull request #3546 added support for choosing the type of search suggestions to + // show, replacing the on-off switch used before, so migrate the previous user choice + + final String showSearchSuggestionsKey = + context.getString(R.string.show_search_suggestions_key); + final Set showSearchSuggestionsValueList = new HashSet<>(); + if (sp.getBoolean(showSearchSuggestionsKey, true)) { + // if the preference was true, all suggestions will be shown, otherwise none + Collections.addAll(showSearchSuggestionsValueList, context.getResources() + .getStringArray(R.array.show_search_suggestions_value_list)); + } + + sp.edit().putStringSet( + showSearchSuggestionsKey, showSearchSuggestionsValueList).apply(); + } + }; + /** * List of all implemented migrations. *

@@ -81,7 +105,8 @@ public final class SettingMigrations { private static final Migration[] SETTING_MIGRATIONS = { MIGRATION_0_1, MIGRATION_1_2, - MIGRATION_2_3 + MIGRATION_2_3, + MIGRATION_3_4, }; diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index e0e0a613a..45400d667 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -227,6 +227,16 @@ show_search_suggestions + show_local_search_suggestions + show_remote_search_suggestions + + @string/show_local_search_suggestions_key + @string/show_remote_search_suggestions_key + + + @string/local_search_suggestions + @string/remote_search_suggestions + show_play_with_kodi show_comments show_next_video diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b346ca1e..733f9a7b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -116,7 +116,9 @@ Player gesture controls Use gestures to control player brightness and volume Search suggestions - Show suggestions when searching + Choose the suggestions to show when searching + Local search suggestions + Remote search suggestions Search history Store search queries locally Watch history diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml index 29863be97..23b782ffd 100644 --- a/app/src/main/res/xml/content_settings.xml +++ b/app/src/main/res/xml/content_settings.xml @@ -65,11 +65,13 @@ app:singleLineTitle="false" app:iconSpaceReserved="false" /> - From d37ee1e0dc011e01c8a7c8508d182774b95f5720 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 17 Mar 2021 21:16:13 +0100 Subject: [PATCH 2/3] First run migrations, then setDefaultValues, since the latter requires the correct types --- .../newpipe/settings/NewPipeSettings.java | 6 ++-- .../newpipe/settings/SettingMigrations.java | 28 +++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java index de39bf755..aa21c4422 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java +++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java @@ -60,6 +60,10 @@ public final class NewPipeSettings { isFirstRun = true; } + // first run migrations, then setDefaultValues, since the latter requires the correct types + SettingMigrations.initMigrations(context, isFirstRun); + + // readAgain is true so that if new settings are added their default value is set PreferenceManager.setDefaultValues(context, R.xml.main_settings, true); PreferenceManager.setDefaultValues(context, R.xml.video_audio_settings, true); PreferenceManager.setDefaultValues(context, R.xml.download_settings, true); @@ -72,8 +76,6 @@ public final class NewPipeSettings { saveDefaultVideoDownloadDirectory(context); saveDefaultAudioDownloadDirectory(context); - - SettingMigrations.initMigrations(context, isFirstRun); } static void saveDefaultVideoDownloadDirectory(final Context context) { diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java index f96c24a40..b0b9567d8 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SettingMigrations.java @@ -19,12 +19,16 @@ import java.util.Set; import static org.schabi.newpipe.MainActivity.DEBUG; +/** + * In order to add a migration, follow these steps, given P is the previous version:
+ * - in the class body add a new {@code MIGRATION_P_P+1 = new Migration(P, P+1) { ... }} and put in + * the {@code migrate()} method the code that need to be run when migrating from P to P+1
+ * - add {@code MIGRATION_P_P+1} at the end of {@link SettingMigrations#SETTING_MIGRATIONS}
+ * - increment {@link SettingMigrations#VERSION}'s value by 1 (so it should become P+1) + */ public final class SettingMigrations { + private static final String TAG = SettingMigrations.class.toString(); - /** - * Version number for preferences. Must be incremented every time a migration is necessary. - */ - public static final int VERSION = 3; private static SharedPreferences sp; public static final Migration MIGRATION_0_1 = new Migration(0, 1) { @@ -84,8 +88,17 @@ public final class SettingMigrations { final String showSearchSuggestionsKey = context.getString(R.string.show_search_suggestions_key); + + boolean addAllSearchSuggestionTypes; + try { + addAllSearchSuggestionTypes = sp.getBoolean(showSearchSuggestionsKey, true); + } catch (final ClassCastException e) { + // just in case it was not a boolean for some reason, let's consider it a "true" + addAllSearchSuggestionTypes = true; + } + final Set showSearchSuggestionsValueList = new HashSet<>(); - if (sp.getBoolean(showSearchSuggestionsKey, true)) { + if (addAllSearchSuggestionTypes) { // if the preference was true, all suggestions will be shown, otherwise none Collections.addAll(showSearchSuggestionsValueList, context.getResources() .getStringArray(R.array.show_search_suggestions_value_list)); @@ -109,6 +122,11 @@ public final class SettingMigrations { MIGRATION_3_4, }; + /** + * Version number for preferences. Must be incremented every time a migration is necessary. + */ + public static final int VERSION = 4; + public static void initMigrations(final Context context, final boolean isFirstRun) { // setup migrations and check if there is something to do From 87e7d959668d5f4f6dacb49be5ea06ee4eb64b92 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 17 Mar 2021 21:36:02 +0100 Subject: [PATCH 3/3] Do not show suggestions error snackbar for interrupted I/O Fix formatting --- .../newpipe/fragments/list/search/SearchFragment.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 8b596e3a8..5ff2f4788 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -55,6 +55,7 @@ import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQu import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.ktx.AnimationType; +import org.schabi.newpipe.ktx.ExceptionUtils; import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.Constants; @@ -782,7 +783,8 @@ public class SearchFragment extends BaseListFragment= THRESHOLD_NETWORK_SUGGESTION; if (showLocalSuggestions && shallShowRemoteSuggestionsNow) { - return Observable.zip(getLocalSuggestionsObservable(query, 3), + return Observable.zip( + getLocalSuggestionsObservable(query, 3), getRemoteSuggestionsObservable(query), (local, remote) -> { remote.removeIf(remoteItem -> local.stream().anyMatch( @@ -810,8 +812,10 @@ public class SearchFragment extends BaseListFragment