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" /> -