Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
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;
Expand All @@ -65,16 +66,19 @@

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;
Expand Down Expand Up @@ -143,7 +147,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
@Nullable private Map<Integer, String> 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;
Expand Down Expand Up @@ -194,26 +199,14 @@ private void setSearchOnResume() {
public void onAttach(@NonNull final Context context) {
super.onAttach(context);

suggestionListAdapter = new SuggestionListAdapter(activity);
final SharedPreferences preferences
= PreferenceManager.getDefaultSharedPreferences(activity);
final boolean isSearchHistoryEnabled = preferences
.getBoolean(getString(R.string.enable_search_history_key), true);
suggestionListAdapter.setShowSuggestionHistory(isSearchHistoryEnabled);
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
showLocalSuggestions = NewPipeSettings.showLocalSearchSuggestions(activity, prefs);
showRemoteSuggestions = NewPipeSettings.showRemoteSearchSuggestions(activity, prefs);

suggestionListAdapter = new SuggestionListAdapter(activity);
historyRecordManager = new HistoryRecordManager(context);
}

@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

final SharedPreferences preferences
= PreferenceManager.getDefaultSharedPreferences(activity);
isSuggestionsEnabled = preferences
.getBoolean(getString(R.string.show_search_suggestions_key), true);
}

@Override
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
Expand Down Expand Up @@ -554,7 +547,7 @@ private void initSearchListeners() {
if (DEBUG) {
Log.d(TAG, "onClick() called with: v = [" + v + "]");
}
if (isSuggestionsEnabled && !isErrorPanelVisible()) {
if ((showLocalSuggestions || showRemoteSuggestions) && !isErrorPanelVisible()) {
showSuggestionsPanel();
}
if (DeviceUtils.isTv(getContext())) {
Expand All @@ -567,7 +560,8 @@ private void initSearchListeners() {
Log.d(TAG, "onFocusChange() called with: "
+ "v = [" + v + "], hasFocus = [" + hasFocus + "]");
}
if (isSuggestionsEnabled && hasFocus && !isErrorPanelVisible()) {
if ((showLocalSuggestions || showRemoteSuggestions)
&& hasFocus && !isErrorPanelVisible()) {
showSuggestionsPanel();
}
});
Expand Down Expand Up @@ -743,6 +737,34 @@ public boolean onBackPressed() {
return false;
}


private Observable<List<SuggestionItem>> getLocalSuggestionsObservable(
final String query, final int similarQueryLimit) {
return historyRecordManager
.getRelatedSearches(query, similarQueryLimit, 25)
.toObservable()
.map(searchHistoryEntries -> {
final Set<SuggestionItem> result = new HashSet<>(); // remove duplicates
for (final SearchHistoryEntry entry : searchHistoryEntries) {
result.add(new SuggestionItem(true, entry.getSearch()));
}
return new ArrayList<>(result);
});
}

private Observable<List<SuggestionItem>> getRemoteSuggestionsObservable(final String query) {
return ExtractorHelper
.suggestionsFor(serviceId, query)
.toObservable()
.map(strings -> {
final List<SuggestionItem> 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");
Expand All @@ -753,70 +775,47 @@ private void initSuggestionObserver() {

suggestionDisposable = suggestionPublisher
.debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS)
.startWithItem(searchString != null
? searchString
: "")
.filter(ss -> isSuggestionsEnabled)
.startWithItem(searchString == null ? "" : searchString)
.switchMap(query -> {
final Flowable<List<SearchHistoryEntry>> flowable = historyRecordManager
.getRelatedSearches(query, 3, 25);
final Observable<List<SuggestionItem>> local = flowable.toObservable()
.map(searchHistoryEntries -> {
final List<SuggestionItem> result = new ArrayList<>();
for (final SearchHistoryEntry entry : searchHistoryEntries) {
result.add(new SuggestionItem(true, entry.getSearch()));
}
return result;
});

if (query.length() < THRESHOLD_NETWORK_SUGGESTION) {
// Only pass through if the query length
// is equal or greater than THRESHOLD_NETWORK_SUGGESTION
return local.materialize();
// 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 (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::<SuggestionItem>emptyList)
.toObservable()
.materialize();
}

final Observable<List<SuggestionItem>> 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<SuggestionItem> 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<SuggestionItem> 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());
} else if (listNotification.isOnError()) {
showError(new ErrorInfo(listNotification.getError(),
if (listNotification.getValue() != null) {
handleSuggestions(listNotification.getValue());
}
} else if (listNotification.isOnError()
&& listNotification.getError() != null
&& !ExceptionUtils.isInterruptedCaused(listNotification.getError())) {
showSnackBarError(new ErrorInfo(listNotification.getError(),
UserAction.GET_SUGGESTIONS, searchString, serviceId));
}
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,6 +11,20 @@ public SuggestionItem(final boolean fromHistory, final String query) {
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 + "]";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,21 @@ public class SuggestionListAdapter
private final ArrayList<SuggestionItem> items = new ArrayList<>();
private final Context context;
private OnSuggestionItemSelected listener;
private boolean showSuggestionHistory = true;

public SuggestionListAdapter(final Context context) {
this.context = context;
}

public void setItems(final List<SuggestionItem> 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();
}

public void setListener(final OnSuggestionItemSelected listener) {
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro
.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 -> {
Expand Down Expand Up @@ -147,8 +146,8 @@ public void onDestroy() {
.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)
Expand Down
32 changes: 30 additions & 2 deletions app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import android.os.Environment;

import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager;

import org.schabi.newpipe.R;
Expand Down Expand Up @@ -59,6 +60,10 @@ public static void initSettings(final Context context) {
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);
Expand All @@ -71,8 +76,6 @@ public static void initSettings(final Context context) {

saveDefaultVideoDownloadDirectory(context);
saveDefaultAudioDownloadDirectory(context);

SettingMigrations.initMigrations(context, isFirstRun);
}

static void saveDefaultVideoDownloadDirectory(final Context context) {
Expand Down Expand Up @@ -124,4 +127,29 @@ public static boolean useStorageAccessFramework(final Context context) {

return prefs.getBoolean(key, true);
}

private static boolean showSearchSuggestions(final Context context,
final SharedPreferences sharedPreferences,
@StringRes final int key) {
final Set<String> 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);
}
}
Loading