mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-01-24 07:56:57 +00:00
Merge pull request #7586 from litetex/add-preference-search
Made preferences searchable
This commit is contained in:
commit
dfa606ef49
4
.gitignore
vendored
4
.gitignore
vendored
@ -8,8 +8,8 @@ captures/
|
|||||||
*~
|
*~
|
||||||
.weblate
|
.weblate
|
||||||
*.class
|
*.class
|
||||||
**/debug/
|
app/debug/
|
||||||
**/release/
|
app/release/
|
||||||
|
|
||||||
# vscode / eclipse files
|
# vscode / eclipse files
|
||||||
*.classpath
|
*.classpath
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import leakcanary.LeakCanary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build variant dependent (BVD) leak canary API implementation for the debug settings fragment.
|
||||||
|
* This class is loaded via reflection by
|
||||||
|
* {@link DebugSettingsFragment.DebugSettingsBVDLeakCanaryAPI}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused") // Class is used but loaded via reflection
|
||||||
|
public class DebugSettingsBVDLeakCanary
|
||||||
|
implements DebugSettingsFragment.DebugSettingsBVDLeakCanaryAPI {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Intent getNewLeakDisplayActivityIntent() {
|
||||||
|
return LeakCanary.INSTANCE.newLeakDisplayActivityIntent();
|
||||||
|
}
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
package org.schabi.newpipe.settings;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import androidx.preference.Preference;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
import org.schabi.newpipe.error.ErrorInfo;
|
|
||||||
import org.schabi.newpipe.error.ErrorUtil;
|
|
||||||
import org.schabi.newpipe.error.UserAction;
|
|
||||||
import org.schabi.newpipe.util.PicassoHelper;
|
|
||||||
|
|
||||||
import leakcanary.LeakCanary;
|
|
||||||
|
|
||||||
public class DebugSettingsFragment extends BasePreferenceFragment {
|
|
||||||
@Override
|
|
||||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
|
||||||
addPreferencesFromResource(R.xml.debug_settings);
|
|
||||||
|
|
||||||
final Preference showMemoryLeaksPreference
|
|
||||||
= findPreference(getString(R.string.show_memory_leaks_key));
|
|
||||||
final Preference showImageIndicatorsPreference
|
|
||||||
= findPreference(getString(R.string.show_image_indicators_key));
|
|
||||||
final Preference crashTheAppPreference
|
|
||||||
= findPreference(getString(R.string.crash_the_app_key));
|
|
||||||
final Preference showErrorSnackbarPreference
|
|
||||||
= findPreference(getString(R.string.show_error_snackbar_key));
|
|
||||||
final Preference createErrorNotificationPreference
|
|
||||||
= findPreference(getString(R.string.create_error_notification_key));
|
|
||||||
|
|
||||||
assert showMemoryLeaksPreference != null;
|
|
||||||
assert showImageIndicatorsPreference != null;
|
|
||||||
assert crashTheAppPreference != null;
|
|
||||||
assert showErrorSnackbarPreference != null;
|
|
||||||
assert createErrorNotificationPreference != null;
|
|
||||||
|
|
||||||
showMemoryLeaksPreference.setOnPreferenceClickListener(preference -> {
|
|
||||||
startActivity(LeakCanary.INSTANCE.newLeakDisplayActivityIntent());
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
showImageIndicatorsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
|
||||||
PicassoHelper.setIndicatorsEnabled((Boolean) newValue);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
crashTheAppPreference.setOnPreferenceClickListener(preference -> {
|
|
||||||
throw new RuntimeException();
|
|
||||||
});
|
|
||||||
|
|
||||||
showErrorSnackbarPreference.setOnPreferenceClickListener(preference -> {
|
|
||||||
ErrorUtil.showUiErrorSnackbar(DebugSettingsFragment.this,
|
|
||||||
"Dummy", new RuntimeException("Dummy"));
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
createErrorNotificationPreference.setOnPreferenceClickListener(preference -> {
|
|
||||||
ErrorUtil.createNotification(requireContext(),
|
|
||||||
new ErrorInfo(new RuntimeException("Dummy"), UserAction.UI_ERROR, "Dummy"));
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.commons.text.similarity;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A matching algorithm that is similar to the searching algorithms implemented in editors such
|
||||||
|
* as Sublime Text, TextMate, Atom and others.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* One point is given for every matched character. Subsequent matches yield two bonus points.
|
||||||
|
* A higher score indicates a higher similarity.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This code has been adapted from Apache Commons Lang 3.3.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @since 1.0
|
||||||
|
*
|
||||||
|
* Note: This class was forked from
|
||||||
|
* <a href="https://git.io/JyYJg">
|
||||||
|
* apache/commons-text (8cfdafc) FuzzyScore.java
|
||||||
|
* </a>
|
||||||
|
*/
|
||||||
|
public class FuzzyScore {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locale used to change the case of text.
|
||||||
|
*/
|
||||||
|
private final Locale locale;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This returns a {@link Locale}-specific {@link FuzzyScore}.
|
||||||
|
*
|
||||||
|
* @param locale The string matching logic is case insensitive.
|
||||||
|
A {@link Locale} is necessary to normalize both Strings to lower case.
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* This is thrown if the {@link Locale} parameter is {@code null}.
|
||||||
|
*/
|
||||||
|
public FuzzyScore(final Locale locale) {
|
||||||
|
if (locale == null) {
|
||||||
|
throw new IllegalArgumentException("Locale must not be null");
|
||||||
|
}
|
||||||
|
this.locale = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the Fuzzy Score which indicates the similarity score between two
|
||||||
|
* Strings.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* score.fuzzyScore(null, null) = IllegalArgumentException
|
||||||
|
* score.fuzzyScore("not null", null) = IllegalArgumentException
|
||||||
|
* score.fuzzyScore(null, "not null") = IllegalArgumentException
|
||||||
|
* score.fuzzyScore("", "") = 0
|
||||||
|
* score.fuzzyScore("Workshop", "b") = 0
|
||||||
|
* score.fuzzyScore("Room", "o") = 1
|
||||||
|
* score.fuzzyScore("Workshop", "w") = 1
|
||||||
|
* score.fuzzyScore("Workshop", "ws") = 2
|
||||||
|
* score.fuzzyScore("Workshop", "wo") = 4
|
||||||
|
* score.fuzzyScore("Apache Software Foundation", "asf") = 3
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param term a full term that should be matched against, must not be null
|
||||||
|
* @param query the query that will be matched against a term, must not be
|
||||||
|
* null
|
||||||
|
* @return result score
|
||||||
|
* @throws IllegalArgumentException if the term or query is {@code null}
|
||||||
|
*/
|
||||||
|
public Integer fuzzyScore(final CharSequence term, final CharSequence query) {
|
||||||
|
if (term == null || query == null) {
|
||||||
|
throw new IllegalArgumentException("CharSequences must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
// fuzzy logic is case insensitive. We normalize the Strings to lower
|
||||||
|
// case right from the start. Turning characters to lower case
|
||||||
|
// via Character.toLowerCase(char) is unfortunately insufficient
|
||||||
|
// as it does not accept a locale.
|
||||||
|
final String termLowerCase = term.toString().toLowerCase(locale);
|
||||||
|
final String queryLowerCase = query.toString().toLowerCase(locale);
|
||||||
|
|
||||||
|
// the resulting score
|
||||||
|
int score = 0;
|
||||||
|
|
||||||
|
// the position in the term which will be scanned next for potential
|
||||||
|
// query character matches
|
||||||
|
int termIndex = 0;
|
||||||
|
|
||||||
|
// index of the previously matched character in the term
|
||||||
|
int previousMatchingCharacterIndex = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
for (int queryIndex = 0; queryIndex < queryLowerCase.length(); queryIndex++) {
|
||||||
|
final char queryChar = queryLowerCase.charAt(queryIndex);
|
||||||
|
|
||||||
|
boolean termCharacterMatchFound = false;
|
||||||
|
for (; termIndex < termLowerCase.length()
|
||||||
|
&& !termCharacterMatchFound; termIndex++) {
|
||||||
|
final char termChar = termLowerCase.charAt(termIndex);
|
||||||
|
|
||||||
|
if (queryChar == termChar) {
|
||||||
|
// simple character matches result in one point
|
||||||
|
score++;
|
||||||
|
|
||||||
|
// subsequent character matches further improve
|
||||||
|
// the score.
|
||||||
|
if (previousMatchingCharacterIndex + 1 == termIndex) {
|
||||||
|
score += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
previousMatchingCharacterIndex = termIndex;
|
||||||
|
|
||||||
|
// we can leave the nested loop. Every character in the
|
||||||
|
// query can match at most one character in the term.
|
||||||
|
termCharacterMatchFound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the locale.
|
||||||
|
*
|
||||||
|
* @return The locale
|
||||||
|
*/
|
||||||
|
public Locale getLocale() {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -185,7 +185,11 @@ class AboutActivity : AppCompatActivity() {
|
|||||||
SoftwareComponent(
|
SoftwareComponent(
|
||||||
"RxJava", "2016 - 2020", "RxJava Contributors",
|
"RxJava", "2016 - 2020", "RxJava Contributors",
|
||||||
"https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2
|
"https://github.com/ReactiveX/RxJava", StandardLicenses.APACHE2
|
||||||
)
|
),
|
||||||
|
SoftwareComponent(
|
||||||
|
"SearchPreference", "2018", "ByteHamster",
|
||||||
|
"https://github.com/ByteHamster/SearchPreference", StandardLicenses.MIT
|
||||||
|
),
|
||||||
)
|
)
|
||||||
private const val POS_ABOUT = 0
|
private const val POS_ABOUT = 0
|
||||||
private const val POS_LICENSE = 1
|
private const val POS_LICENSE = 1
|
||||||
|
@ -25,7 +25,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.DecelerateInterpolator;
|
import android.view.animation.DecelerateInterpolator;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
@ -34,7 +33,6 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.TooltipCompat;
|
import androidx.appcompat.widget.TooltipCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.core.text.HtmlCompat;
|
import androidx.core.text.HtmlCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
@ -65,6 +63,7 @@ import org.schabi.newpipe.settings.NewPipeSettings;
|
|||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
|
import org.schabi.newpipe.util.KeyboardUtil;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
|
||||||
@ -670,31 +669,15 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "showKeyboardSearch() called");
|
Log.d(TAG, "showKeyboardSearch() called");
|
||||||
}
|
}
|
||||||
if (searchEditText == null) {
|
KeyboardUtil.showKeyboard(activity, searchEditText);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchEditText.requestFocus()) {
|
|
||||||
final InputMethodManager imm = ContextCompat.getSystemService(activity,
|
|
||||||
InputMethodManager.class);
|
|
||||||
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideKeyboardSearch() {
|
private void hideKeyboardSearch() {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "hideKeyboardSearch() called");
|
Log.d(TAG, "hideKeyboardSearch() called");
|
||||||
}
|
}
|
||||||
if (searchEditText == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final InputMethodManager imm = ContextCompat.getSystemService(activity,
|
KeyboardUtil.hideKeyboard(activity, searchEditText);
|
||||||
InputMethodManager.class);
|
|
||||||
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
|
|
||||||
InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
|
||||||
|
|
||||||
searchEditText.clearFocus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showDeleteSuggestionDialog(final SuggestionItem item) {
|
private void showDeleteSuggestionDialog(final SuggestionItem item) {
|
||||||
|
@ -2,7 +2,6 @@ package org.schabi.newpipe.settings;
|
|||||||
|
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
@ -15,14 +14,10 @@ import org.schabi.newpipe.util.Constants;
|
|||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
public class AppearanceSettingsFragment extends BasePreferenceFragment {
|
public class AppearanceSettingsFragment extends BasePreferenceFragment {
|
||||||
private static final boolean CAPTIONING_SETTINGS_ACCESSIBLE =
|
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
|
||||||
|
|
||||||
private String captionSettingsKey;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||||
addPreferencesFromResource(R.xml.appearance_settings);
|
addPreferencesFromResourceRegistry();
|
||||||
|
|
||||||
final String themeKey = getString(R.string.theme_key);
|
final String themeKey = getString(R.string.theme_key);
|
||||||
// the key of the active theme when settings were opened (or recreated after theme change)
|
// the key of the active theme when settings were opened (or recreated after theme change)
|
||||||
@ -51,16 +46,11 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
|
|||||||
} else {
|
} else {
|
||||||
removePreference(nightThemeKey);
|
removePreference(nightThemeKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
captionSettingsKey = getString(R.string.caption_settings_key);
|
|
||||||
if (!CAPTIONING_SETTINGS_ACCESSIBLE) {
|
|
||||||
removePreference(captionSettingsKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceTreeClick(final Preference preference) {
|
public boolean onPreferenceTreeClick(final Preference preference) {
|
||||||
if (preference.getKey().equals(captionSettingsKey) && CAPTIONING_SETTINGS_ACCESSIBLE) {
|
if (preference.getKey().equals(getString(R.string.caption_settings_key))) {
|
||||||
try {
|
try {
|
||||||
startActivity(new Intent(Settings.ACTION_CAPTIONING_SETTINGS));
|
startActivity(new Intent(Settings.ACTION_CAPTIONING_SETTINGS));
|
||||||
} catch (final ActivityNotFoundException e) {
|
} catch (final ActivityNotFoundException e) {
|
||||||
|
@ -28,6 +28,11 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void addPreferencesFromResourceRegistry() {
|
||||||
|
addPreferencesFromResource(
|
||||||
|
SettingsResourceRegistry.getInstance().getPreferencesResId(this.getClass()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull final View rootView,
|
public void onViewCreated(@NonNull final View rootView,
|
||||||
@Nullable final Bundle savedInstanceState) {
|
@Nullable final Bundle savedInstanceState) {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package org.schabi.newpipe.settings;
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
|
||||||
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -21,7 +24,6 @@ import org.schabi.newpipe.DownloaderImpl;
|
|||||||
import org.schabi.newpipe.NewPipeDatabase;
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.error.ErrorUtil;
|
import org.schabi.newpipe.error.ErrorUtil;
|
||||||
import org.schabi.newpipe.error.ReCaptchaActivity;
|
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||||
import org.schabi.newpipe.extractor.localization.Localization;
|
import org.schabi.newpipe.extractor.localization.Localization;
|
||||||
@ -38,9 +40,6 @@ import java.util.Date;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
||||||
|
|
||||||
public class ContentSettingsFragment extends BasePreferenceFragment {
|
public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||||
private static final String ZIP_MIME_TYPE = "application/zip";
|
private static final String ZIP_MIME_TYPE = "application/zip";
|
||||||
|
|
||||||
@ -70,7 +69,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
|||||||
importExportDataPathKey = getString(R.string.import_export_data_path);
|
importExportDataPathKey = getString(R.string.import_export_data_path);
|
||||||
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
|
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
|
||||||
|
|
||||||
addPreferencesFromResource(R.xml.content_settings);
|
addPreferencesFromResourceRegistry();
|
||||||
|
|
||||||
final Preference importDataPreference = requirePreference(R.string.import_data);
|
final Preference importDataPreference = requirePreference(R.string.import_data);
|
||||||
importDataPreference.setOnPreferenceClickListener((Preference p) -> {
|
importDataPreference.setOnPreferenceClickListener((Preference p) -> {
|
||||||
@ -105,21 +104,6 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
|||||||
.getPreferredContentCountry(requireContext());
|
.getPreferredContentCountry(requireContext());
|
||||||
initialLanguage = defaultPreferences.getString(getString(R.string.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 -> {
|
|
||||||
defaultPreferences.edit()
|
|
||||||
.putString(getString(R.string.recaptcha_cookies_key), "").apply();
|
|
||||||
DownloaderImpl.getInstance().setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, "");
|
|
||||||
Toast.makeText(getActivity(), R.string.recaptcha_cookies_cleared,
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
clearCookiePref.setVisible(false);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (defaultPreferences.getString(getString(R.string.recaptcha_cookies_key), "").isEmpty()) {
|
|
||||||
clearCookiePref.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
findPreference(getString(R.string.download_thumbnail_key)).setOnPreferenceChangeListener(
|
findPreference(getString(R.string.download_thumbnail_key)).setOnPreferenceChangeListener(
|
||||||
(preference, newValue) -> {
|
(preference, newValue) -> {
|
||||||
PicassoHelper.setShouldLoadImages((Boolean) newValue);
|
PicassoHelper.setShouldLoadImages((Boolean) newValue);
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
|
import org.schabi.newpipe.error.ErrorUtil;
|
||||||
|
import org.schabi.newpipe.error.UserAction;
|
||||||
|
import org.schabi.newpipe.util.PicassoHelper;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class DebugSettingsFragment extends BasePreferenceFragment {
|
||||||
|
private static final String DUMMY = "Dummy";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||||
|
addPreferencesFromResourceRegistry();
|
||||||
|
|
||||||
|
final Preference allowHeapDumpingPreference
|
||||||
|
= findPreference(getString(R.string.allow_heap_dumping_key));
|
||||||
|
final Preference showMemoryLeaksPreference
|
||||||
|
= findPreference(getString(R.string.show_memory_leaks_key));
|
||||||
|
final Preference showImageIndicatorsPreference
|
||||||
|
= findPreference(getString(R.string.show_image_indicators_key));
|
||||||
|
final Preference crashTheAppPreference
|
||||||
|
= findPreference(getString(R.string.crash_the_app_key));
|
||||||
|
final Preference showErrorSnackbarPreference
|
||||||
|
= findPreference(getString(R.string.show_error_snackbar_key));
|
||||||
|
final Preference createErrorNotificationPreference
|
||||||
|
= findPreference(getString(R.string.create_error_notification_key));
|
||||||
|
|
||||||
|
assert allowHeapDumpingPreference != null;
|
||||||
|
assert showMemoryLeaksPreference != null;
|
||||||
|
assert showImageIndicatorsPreference != null;
|
||||||
|
assert crashTheAppPreference != null;
|
||||||
|
assert showErrorSnackbarPreference != null;
|
||||||
|
assert createErrorNotificationPreference != null;
|
||||||
|
|
||||||
|
final Optional<DebugSettingsBVDLeakCanaryAPI> optBVLeakCanary = getBVDLeakCanary();
|
||||||
|
|
||||||
|
allowHeapDumpingPreference.setEnabled(optBVLeakCanary.isPresent());
|
||||||
|
showMemoryLeaksPreference.setEnabled(optBVLeakCanary.isPresent());
|
||||||
|
|
||||||
|
if (optBVLeakCanary.isPresent()) {
|
||||||
|
final DebugSettingsBVDLeakCanaryAPI pdLeakCanary = optBVLeakCanary.get();
|
||||||
|
|
||||||
|
showMemoryLeaksPreference.setOnPreferenceClickListener(preference -> {
|
||||||
|
startActivity(pdLeakCanary.getNewLeakDisplayActivityIntent());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
allowHeapDumpingPreference.setSummary(R.string.leak_canary_not_available);
|
||||||
|
showMemoryLeaksPreference.setSummary(R.string.leak_canary_not_available);
|
||||||
|
}
|
||||||
|
|
||||||
|
showImageIndicatorsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
PicassoHelper.setIndicatorsEnabled((Boolean) newValue);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
crashTheAppPreference.setOnPreferenceClickListener(preference -> {
|
||||||
|
throw new RuntimeException(DUMMY);
|
||||||
|
});
|
||||||
|
|
||||||
|
showErrorSnackbarPreference.setOnPreferenceClickListener(preference -> {
|
||||||
|
ErrorUtil.showUiErrorSnackbar(DebugSettingsFragment.this,
|
||||||
|
DUMMY, new RuntimeException(DUMMY));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
createErrorNotificationPreference.setOnPreferenceClickListener(preference -> {
|
||||||
|
ErrorUtil.createNotification(requireContext(),
|
||||||
|
new ErrorInfo(new RuntimeException(DUMMY), UserAction.UI_ERROR, DUMMY));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to find the {@link DebugSettingsBVDLeakCanaryAPI#IMPL_CLASS} and loads it if available.
|
||||||
|
* @return An {@link Optional} which is empty if the implementation class couldn't be loaded.
|
||||||
|
*/
|
||||||
|
private Optional<DebugSettingsBVDLeakCanaryAPI> getBVDLeakCanary() {
|
||||||
|
try {
|
||||||
|
// Try to find the implementation of the LeakCanary API
|
||||||
|
return Optional.of((DebugSettingsBVDLeakCanaryAPI)
|
||||||
|
Class.forName(DebugSettingsBVDLeakCanaryAPI.IMPL_CLASS)
|
||||||
|
.getDeclaredConstructor()
|
||||||
|
.newInstance());
|
||||||
|
} catch (final Exception e) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build variant dependent (BVD) leak canary API for this fragment.
|
||||||
|
* Why is LeakCanary not used directly? Because it can't be assured
|
||||||
|
*/
|
||||||
|
public interface DebugSettingsBVDLeakCanaryAPI {
|
||||||
|
String IMPL_CLASS =
|
||||||
|
"org.schabi.newpipe.settings.DebugSettingsBVDLeakCanary";
|
||||||
|
|
||||||
|
Intent getNewLeakDisplayActivityIntent();
|
||||||
|
}
|
||||||
|
}
|
@ -54,7 +54,7 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||||
addPreferencesFromResource(R.xml.download_settings);
|
addPreferencesFromResourceRegistry();
|
||||||
|
|
||||||
downloadPathVideoPreference = getString(R.string.download_path_video_key);
|
downloadPathVideoPreference = getString(R.string.download_path_video_key);
|
||||||
downloadPathAudioPreference = getString(R.string.download_path_audio_key);
|
downloadPathAudioPreference = getString(R.string.download_path_audio_key);
|
||||||
|
@ -8,9 +8,11 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.DownloaderImpl;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.error.ErrorInfo;
|
import org.schabi.newpipe.error.ErrorInfo;
|
||||||
import org.schabi.newpipe.error.ErrorUtil;
|
import org.schabi.newpipe.error.ErrorUtil;
|
||||||
|
import org.schabi.newpipe.error.ReCaptchaActivity;
|
||||||
import org.schabi.newpipe.error.UserAction;
|
import org.schabi.newpipe.error.UserAction;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.util.InfoCache;
|
import org.schabi.newpipe.util.InfoCache;
|
||||||
@ -29,7 +31,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||||
addPreferencesFromResource(R.xml.history_settings);
|
addPreferencesFromResourceRegistry();
|
||||||
|
|
||||||
cacheWipeKey = getString(R.string.metadata_cache_wipe_key);
|
cacheWipeKey = getString(R.string.metadata_cache_wipe_key);
|
||||||
viewsHistoryClearKey = getString(R.string.clear_views_history_key);
|
viewsHistoryClearKey = getString(R.string.clear_views_history_key);
|
||||||
@ -37,6 +39,21 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
|
|||||||
searchHistoryClearKey = getString(R.string.clear_search_history_key);
|
searchHistoryClearKey = getString(R.string.clear_search_history_key);
|
||||||
recordManager = new HistoryRecordManager(getActivity());
|
recordManager = new HistoryRecordManager(getActivity());
|
||||||
disposables = new CompositeDisposable();
|
disposables = new CompositeDisposable();
|
||||||
|
|
||||||
|
final Preference clearCookiePref = requirePreference(R.string.clear_cookie_key);
|
||||||
|
clearCookiePref.setOnPreferenceClickListener(preference -> {
|
||||||
|
defaultPreferences.edit()
|
||||||
|
.putString(getString(R.string.recaptcha_cookies_key), "").apply();
|
||||||
|
DownloaderImpl.getInstance().setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, "");
|
||||||
|
Toast.makeText(getActivity(), R.string.recaptcha_cookies_cleared,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
clearCookiePref.setEnabled(false);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (defaultPreferences.getString(getString(R.string.recaptcha_cookies_key), "").isEmpty()) {
|
||||||
|
clearCookiePref.setEnabled(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package org.schabi.newpipe.settings;
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import androidx.preference.Preference;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.schabi.newpipe.App;
|
import org.schabi.newpipe.App;
|
||||||
import org.schabi.newpipe.CheckForNewAppVersion;
|
import org.schabi.newpipe.CheckForNewAppVersion;
|
||||||
@ -12,16 +15,58 @@ import org.schabi.newpipe.R;
|
|||||||
public class MainSettingsFragment extends BasePreferenceFragment {
|
public class MainSettingsFragment extends BasePreferenceFragment {
|
||||||
public static final boolean DEBUG = MainActivity.DEBUG;
|
public static final boolean DEBUG = MainActivity.DEBUG;
|
||||||
|
|
||||||
|
private SettingsActivity settingsActivity;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||||
addPreferencesFromResource(R.xml.main_settings);
|
addPreferencesFromResourceRegistry();
|
||||||
|
|
||||||
|
setHasOptionsMenu(true); // Otherwise onCreateOptionsMenu is not called
|
||||||
|
|
||||||
|
// Check if the app is updatable
|
||||||
if (!CheckForNewAppVersion.isReleaseApk(App.getApp())) {
|
if (!CheckForNewAppVersion.isReleaseApk(App.getApp())) {
|
||||||
final Preference update
|
getPreferenceScreen().removePreference(
|
||||||
= findPreference(getString(R.string.update_pref_screen_key));
|
findPreference(getString(R.string.update_pref_screen_key)));
|
||||||
getPreferenceScreen().removePreference(update);
|
|
||||||
|
|
||||||
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), false).apply();
|
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), false).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide debug preferences in RELEASE build variant
|
||||||
|
if (!DEBUG) {
|
||||||
|
getPreferenceScreen().removePreference(
|
||||||
|
findPreference(getString(R.string.debug_pref_screen_key)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(
|
||||||
|
@NonNull final Menu menu,
|
||||||
|
@NonNull final MenuInflater inflater
|
||||||
|
) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
|
||||||
|
// -- Link settings activity and register menu --
|
||||||
|
settingsActivity = (SettingsActivity) getActivity();
|
||||||
|
|
||||||
|
inflater.inflate(R.menu.menu_settings_main_fragment, menu);
|
||||||
|
|
||||||
|
final MenuItem menuSearchItem = menu.getItem(0);
|
||||||
|
|
||||||
|
settingsActivity.setMenuSearchItem(menuSearchItem);
|
||||||
|
|
||||||
|
menuSearchItem.setOnMenuItemClickListener(ev -> {
|
||||||
|
settingsActivity.setSearchActive(true);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
// Unlink activity so that we don't get memory problems
|
||||||
|
if (settingsActivity != null) {
|
||||||
|
settingsActivity.setMenuSearchItem(null);
|
||||||
|
settingsActivity = null;
|
||||||
|
}
|
||||||
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import org.schabi.newpipe.R
|
|||||||
|
|
||||||
class NotificationSettingsFragment : BasePreferenceFragment() {
|
class NotificationSettingsFragment : BasePreferenceFragment() {
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
addPreferencesFromResource(R.xml.notification_settings)
|
addPreferencesFromResourceRegistry()
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||||
val colorizePref: Preference? = findPreference(getString(R.string.notification_colorize_key))
|
val colorizePref: Preference? = findPreference(getString(R.string.notification_colorize_key))
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.schabi.newpipe.settings;
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -51,16 +50,11 @@ public class SelectKioskFragment extends DialogFragment {
|
|||||||
private SelectKioskAdapter selectKioskAdapter = null;
|
private SelectKioskAdapter selectKioskAdapter = null;
|
||||||
|
|
||||||
private OnSelectedListener onSelectedListener = null;
|
private OnSelectedListener onSelectedListener = null;
|
||||||
private OnCancelListener onCancelListener = null;
|
|
||||||
|
|
||||||
public void setOnSelectedListener(final OnSelectedListener listener) {
|
public void setOnSelectedListener(final OnSelectedListener listener) {
|
||||||
onSelectedListener = listener;
|
onSelectedListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOnCancelListener(final OnCancelListener listener) {
|
|
||||||
onCancelListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Init
|
// Init
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@ -91,14 +85,6 @@ public class SelectKioskFragment extends DialogFragment {
|
|||||||
// Handle actions
|
// Handle actions
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCancel(@NonNull final DialogInterface dialogInterface) {
|
|
||||||
super.onCancel(dialogInterface);
|
|
||||||
if (onCancelListener != null) {
|
|
||||||
onCancelListener.onCancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clickedItem(final SelectKioskAdapter.Entry entry) {
|
private void clickedItem(final SelectKioskAdapter.Entry entry) {
|
||||||
if (onSelectedListener != null) {
|
if (onSelectedListener != null) {
|
||||||
onSelectedListener.onKioskSelected(entry.serviceId, entry.kioskId, entry.kioskName);
|
onSelectedListener.onKioskSelected(entry.serviceId, entry.kioskId, entry.kioskName);
|
||||||
@ -114,10 +100,6 @@ public class SelectKioskFragment extends DialogFragment {
|
|||||||
void onKioskSelected(int serviceId, String kioskId, String kioskName);
|
void onKioskSelected(int serviceId, String kioskId, String kioskName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface OnCancelListener {
|
|
||||||
void onCancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SelectKioskAdapter
|
private class SelectKioskAdapter
|
||||||
extends RecyclerView.Adapter<SelectKioskAdapter.SelectKioskItemHolder> {
|
extends RecyclerView.Adapter<SelectKioskAdapter.SelectKioskItemHolder> {
|
||||||
private final List<Entry> kioskList = new Vector<>();
|
private final List<Entry> kioskList = new Vector<>();
|
||||||
|
@ -1,22 +1,48 @@
|
|||||||
package org.schabi.newpipe.settings;
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import androidx.annotation.IdRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
|
import com.jakewharton.rxbinding4.widget.RxTextView;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.App;
|
||||||
|
import org.schabi.newpipe.CheckForNewAppVersion;
|
||||||
|
import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.databinding.SettingsLayoutBinding;
|
import org.schabi.newpipe.databinding.SettingsLayoutBinding;
|
||||||
|
import org.schabi.newpipe.settings.preferencesearch.PreferenceParser;
|
||||||
|
import org.schabi.newpipe.settings.preferencesearch.PreferenceSearchConfiguration;
|
||||||
|
import org.schabi.newpipe.settings.preferencesearch.PreferenceSearchFragment;
|
||||||
|
import org.schabi.newpipe.settings.preferencesearch.PreferenceSearchItem;
|
||||||
|
import org.schabi.newpipe.settings.preferencesearch.PreferenceSearchResultHighlighter;
|
||||||
|
import org.schabi.newpipe.settings.preferencesearch.PreferenceSearchResultListener;
|
||||||
|
import org.schabi.newpipe.settings.preferencesearch.PreferenceSearcher;
|
||||||
import org.schabi.newpipe.util.DeviceUtils;
|
import org.schabi.newpipe.util.DeviceUtils;
|
||||||
|
import org.schabi.newpipe.util.KeyboardUtil;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
import org.schabi.newpipe.views.FocusOverlayView;
|
import org.schabi.newpipe.views.FocusOverlayView;
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import icepick.Icepick;
|
||||||
|
import icepick.State;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Created by Christian Schabesberger on 31.08.15.
|
* Created by Christian Schabesberger on 31.08.15.
|
||||||
@ -38,21 +64,54 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class SettingsActivity extends AppCompatActivity
|
public class SettingsActivity extends AppCompatActivity implements
|
||||||
implements BasePreferenceFragment.OnPreferenceStartFragmentCallback {
|
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
|
||||||
|
PreferenceSearchResultListener {
|
||||||
|
private static final String TAG = "SettingsActivity";
|
||||||
|
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||||
|
|
||||||
|
@IdRes
|
||||||
|
private static final int FRAGMENT_HOLDER_ID = R.id.settings_fragment_holder;
|
||||||
|
|
||||||
|
private PreferenceSearchFragment searchFragment;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private MenuItem menuSearchItem;
|
||||||
|
|
||||||
|
private View searchContainer;
|
||||||
|
private EditText searchEditText;
|
||||||
|
|
||||||
|
// State
|
||||||
|
@State
|
||||||
|
String searchText;
|
||||||
|
@State
|
||||||
|
boolean wasSearchActive;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceBundle) {
|
protected void onCreate(final Bundle savedInstanceBundle) {
|
||||||
setTheme(ThemeHelper.getSettingsThemeStyle(this));
|
setTheme(ThemeHelper.getSettingsThemeStyle(this));
|
||||||
assureCorrectAppLanguage(this);
|
assureCorrectAppLanguage(this);
|
||||||
|
|
||||||
super.onCreate(savedInstanceBundle);
|
super.onCreate(savedInstanceBundle);
|
||||||
|
Icepick.restoreInstanceState(this, savedInstanceBundle);
|
||||||
|
final boolean restored = savedInstanceBundle != null;
|
||||||
|
|
||||||
final SettingsLayoutBinding settingsLayoutBinding =
|
final SettingsLayoutBinding settingsLayoutBinding =
|
||||||
SettingsLayoutBinding.inflate(getLayoutInflater());
|
SettingsLayoutBinding.inflate(getLayoutInflater());
|
||||||
setContentView(settingsLayoutBinding.getRoot());
|
setContentView(settingsLayoutBinding.getRoot());
|
||||||
|
initSearch(settingsLayoutBinding, restored);
|
||||||
|
|
||||||
setSupportActionBar(settingsLayoutBinding.settingsToolbarLayout.toolbar);
|
setSupportActionBar(settingsLayoutBinding.settingsToolbarLayout.toolbar);
|
||||||
|
|
||||||
if (savedInstanceBundle == null) {
|
if (restored) {
|
||||||
|
// Restore state
|
||||||
|
if (this.wasSearchActive) {
|
||||||
|
setSearchActive(true);
|
||||||
|
if (!TextUtils.isEmpty(this.searchText)) {
|
||||||
|
this.searchEditText.setText(this.searchText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(R.id.settings_fragment_holder, new MainSettingsFragment())
|
.replace(R.id.settings_fragment_holder, new MainSettingsFragment())
|
||||||
.commit();
|
.commit();
|
||||||
@ -63,6 +122,12 @@ public class SettingsActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
Icepick.saveInstanceState(this, outState);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
final ActionBar actionBar = getSupportActionBar();
|
final ActionBar actionBar = getSupportActionBar();
|
||||||
@ -74,10 +139,25 @@ public class SettingsActivity extends AppCompatActivity
|
|||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (isSearchActive()) {
|
||||||
|
setSearchActive(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
final int id = item.getItemId();
|
final int id = item.getItemId();
|
||||||
if (id == android.R.id.home) {
|
if (id == android.R.id.home) {
|
||||||
|
// Check if the search is active and if so: Close it
|
||||||
|
if (isSearchActive()) {
|
||||||
|
setSearchActive(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||||
finish();
|
finish();
|
||||||
} else {
|
} else {
|
||||||
@ -91,14 +171,221 @@ public class SettingsActivity extends AppCompatActivity
|
|||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceStartFragment(final PreferenceFragmentCompat caller,
|
public boolean onPreferenceStartFragment(final PreferenceFragmentCompat caller,
|
||||||
final Preference preference) {
|
final Preference preference) {
|
||||||
final Fragment fragment = Fragment
|
showSettingsFragment(instantiateFragment(preference.getFragment()));
|
||||||
.instantiate(this, preference.getFragment(), preference.getExtras());
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Fragment instantiateFragment(@NonNull final String className) {
|
||||||
|
return getSupportFragmentManager()
|
||||||
|
.getFragmentFactory()
|
||||||
|
.instantiate(this.getClassLoader(), className);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSettingsFragment(final Fragment fragment) {
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out,
|
.setCustomAnimations(R.animator.custom_fade_in, R.animator.custom_fade_out,
|
||||||
R.animator.custom_fade_in, R.animator.custom_fade_out)
|
R.animator.custom_fade_in, R.animator.custom_fade_out)
|
||||||
.replace(R.id.settings_fragment_holder, fragment)
|
.replace(FRAGMENT_HOLDER_ID, fragment)
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit();
|
.commit();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
setMenuSearchItem(null);
|
||||||
|
searchFragment = null;
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Search
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
//region Search
|
||||||
|
|
||||||
|
private void initSearch(
|
||||||
|
final SettingsLayoutBinding settingsLayoutBinding,
|
||||||
|
final boolean restored
|
||||||
|
) {
|
||||||
|
searchContainer =
|
||||||
|
settingsLayoutBinding.settingsToolbarLayout.toolbar
|
||||||
|
.findViewById(R.id.toolbar_search_container);
|
||||||
|
|
||||||
|
// Configure input field for search
|
||||||
|
searchEditText = searchContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||||
|
RxTextView.textChanges(searchEditText)
|
||||||
|
// Wait some time after the last input before actually searching
|
||||||
|
.debounce(200, TimeUnit.MILLISECONDS)
|
||||||
|
.subscribe(v -> runOnUiThread(this::onSearchChanged));
|
||||||
|
|
||||||
|
// Configure clear button
|
||||||
|
searchContainer.findViewById(R.id.toolbar_search_clear)
|
||||||
|
.setOnClickListener(ev -> resetSearchText());
|
||||||
|
|
||||||
|
ensureSearchRepresentsApplicationState();
|
||||||
|
|
||||||
|
// Build search configuration using SettingsResourceRegistry
|
||||||
|
final PreferenceSearchConfiguration config = new PreferenceSearchConfiguration();
|
||||||
|
|
||||||
|
|
||||||
|
// Build search items
|
||||||
|
final PreferenceParser parser = new PreferenceParser(getApplicationContext(), config);
|
||||||
|
final PreferenceSearcher searcher = new PreferenceSearcher(config);
|
||||||
|
|
||||||
|
// Find all searchable SettingsResourceRegistry fragments
|
||||||
|
SettingsResourceRegistry.getInstance().getAllEntries().stream()
|
||||||
|
.filter(SettingsResourceRegistry.SettingRegistryEntry::isSearchable)
|
||||||
|
// Get the resId
|
||||||
|
.map(SettingsResourceRegistry.SettingRegistryEntry::getPreferencesResId)
|
||||||
|
// Parse
|
||||||
|
.map(parser::parse)
|
||||||
|
// Add it to the searcher
|
||||||
|
.forEach(searcher::add);
|
||||||
|
|
||||||
|
if (restored) {
|
||||||
|
searchFragment = (PreferenceSearchFragment) getSupportFragmentManager()
|
||||||
|
.findFragmentByTag(PreferenceSearchFragment.NAME);
|
||||||
|
if (searchFragment != null) {
|
||||||
|
// Hide/Remove the search fragment otherwise we get an exception
|
||||||
|
// when adding it (because it's already present)
|
||||||
|
hideSearchFragment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (searchFragment == null) {
|
||||||
|
searchFragment = new PreferenceSearchFragment();
|
||||||
|
}
|
||||||
|
searchFragment.setSearcher(searcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the search shows the correct/available search results.
|
||||||
|
* <br/>
|
||||||
|
* Some features are e.g. only available for debug builds, these should not
|
||||||
|
* be found when searching inside a release.
|
||||||
|
*/
|
||||||
|
private void ensureSearchRepresentsApplicationState() {
|
||||||
|
// Check if the update settings are available
|
||||||
|
if (!CheckForNewAppVersion.isReleaseApk(App.getApp())) {
|
||||||
|
SettingsResourceRegistry.getInstance()
|
||||||
|
.getEntryByPreferencesResId(R.xml.update_settings)
|
||||||
|
.setSearchable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide debug preferences in RELEASE build variant
|
||||||
|
if (DEBUG) {
|
||||||
|
SettingsResourceRegistry.getInstance()
|
||||||
|
.getEntryByPreferencesResId(R.xml.debug_settings)
|
||||||
|
.setSearchable(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMenuSearchItem(final MenuItem menuSearchItem) {
|
||||||
|
this.menuSearchItem = menuSearchItem;
|
||||||
|
|
||||||
|
// Ensure that the item is in the correct state when adding it. This is due to
|
||||||
|
// Android's lifecycle (the Activity is recreated before the Fragment that registers this)
|
||||||
|
if (menuSearchItem != null) {
|
||||||
|
menuSearchItem.setVisible(!isSearchActive());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchActive(final boolean active) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "setSearchActive called active=" + active);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore if search is already in correct state
|
||||||
|
if (isSearchActive() == active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wasSearchActive = active;
|
||||||
|
|
||||||
|
searchContainer.setVisibility(active ? View.VISIBLE : View.GONE);
|
||||||
|
if (menuSearchItem != null) {
|
||||||
|
menuSearchItem.setVisible(!active);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
getSupportFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.add(FRAGMENT_HOLDER_ID, searchFragment, PreferenceSearchFragment.NAME)
|
||||||
|
.addToBackStack(PreferenceSearchFragment.NAME)
|
||||||
|
.commit();
|
||||||
|
|
||||||
|
KeyboardUtil.showKeyboard(this, searchEditText);
|
||||||
|
} else if (searchFragment != null) {
|
||||||
|
hideSearchFragment();
|
||||||
|
getSupportFragmentManager()
|
||||||
|
.popBackStack(
|
||||||
|
PreferenceSearchFragment.NAME,
|
||||||
|
FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||||
|
|
||||||
|
KeyboardUtil.hideKeyboard(this, searchEditText);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSearchText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideSearchFragment() {
|
||||||
|
getSupportFragmentManager().beginTransaction().remove(searchFragment).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetSearchText() {
|
||||||
|
searchEditText.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSearchActive() {
|
||||||
|
return searchContainer.getVisibility() == View.VISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSearchChanged() {
|
||||||
|
if (!isSearchActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchFragment != null) {
|
||||||
|
searchText = this.searchEditText.getText().toString();
|
||||||
|
searchFragment.updateSearchResults(searchText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSearchResultClicked(@NonNull final PreferenceSearchItem result) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "onSearchResultClicked called result=" + result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the search
|
||||||
|
setSearchActive(false);
|
||||||
|
|
||||||
|
// -- Highlight the result --
|
||||||
|
// Find out which fragment class we need
|
||||||
|
final Class<? extends Fragment> targetedFragmentClass =
|
||||||
|
SettingsResourceRegistry.getInstance()
|
||||||
|
.getFragmentClass(result.getSearchIndexItemResId());
|
||||||
|
|
||||||
|
if (targetedFragmentClass == null) {
|
||||||
|
// This should never happen
|
||||||
|
Log.w(TAG, "Unable to locate fragment class for resId="
|
||||||
|
+ result.getSearchIndexItemResId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the currentFragment is the one which contains the result
|
||||||
|
Fragment currentFragment =
|
||||||
|
getSupportFragmentManager().findFragmentById(FRAGMENT_HOLDER_ID);
|
||||||
|
if (!targetedFragmentClass.equals(currentFragment.getClass())) {
|
||||||
|
// If it's not the correct one display the correct one
|
||||||
|
currentFragment = instantiateFragment(targetedFragmentClass.getName());
|
||||||
|
showSettingsFragment(currentFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the highlighting
|
||||||
|
if (currentFragment instanceof PreferenceFragmentCompat) {
|
||||||
|
PreferenceSearchResultHighlighter
|
||||||
|
.highlight(result, (PreferenceFragmentCompat) currentFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,148 @@
|
|||||||
|
package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.XmlRes;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A registry that contains information about SettingsFragments.
|
||||||
|
* <br/>
|
||||||
|
* includes:
|
||||||
|
* <ul>
|
||||||
|
* <li>Class of the SettingsFragment</li>
|
||||||
|
* <li>XML-Resource</li>
|
||||||
|
* <li>...</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* E.g. used by the preference search.
|
||||||
|
*/
|
||||||
|
public final class SettingsResourceRegistry {
|
||||||
|
|
||||||
|
private static final SettingsResourceRegistry INSTANCE = new SettingsResourceRegistry();
|
||||||
|
|
||||||
|
private final Set<SettingRegistryEntry> registeredEntries = new HashSet<>();
|
||||||
|
|
||||||
|
private SettingsResourceRegistry() {
|
||||||
|
add(MainSettingsFragment.class, R.xml.main_settings).setSearchable(false);
|
||||||
|
|
||||||
|
add(AppearanceSettingsFragment.class, R.xml.appearance_settings);
|
||||||
|
add(ContentSettingsFragment.class, R.xml.content_settings);
|
||||||
|
add(DebugSettingsFragment.class, R.xml.debug_settings).setSearchable(false);
|
||||||
|
add(DownloadSettingsFragment.class, R.xml.download_settings);
|
||||||
|
add(HistorySettingsFragment.class, R.xml.history_settings);
|
||||||
|
add(NotificationSettingsFragment.class, R.xml.notification_settings);
|
||||||
|
add(UpdateSettingsFragment.class, R.xml.update_settings);
|
||||||
|
add(VideoAudioSettingsFragment.class, R.xml.video_audio_settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SettingRegistryEntry add(
|
||||||
|
@NonNull final Class<? extends Fragment> fragmentClass,
|
||||||
|
@XmlRes final int preferencesResId
|
||||||
|
) {
|
||||||
|
final SettingRegistryEntry entry =
|
||||||
|
new SettingRegistryEntry(fragmentClass, preferencesResId);
|
||||||
|
this.registeredEntries.add(entry);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SettingRegistryEntry getEntryByFragmentClass(
|
||||||
|
final Class<? extends Fragment> fragmentClass
|
||||||
|
) {
|
||||||
|
Objects.requireNonNull(fragmentClass);
|
||||||
|
return registeredEntries.stream()
|
||||||
|
.filter(e -> Objects.equals(e.getFragmentClass(), fragmentClass))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SettingRegistryEntry getEntryByPreferencesResId(@XmlRes final int preferencesResId) {
|
||||||
|
return registeredEntries.stream()
|
||||||
|
.filter(e -> Objects.equals(e.getPreferencesResId(), preferencesResId))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPreferencesResId(@NonNull final Class<? extends Fragment> fragmentClass) {
|
||||||
|
final SettingRegistryEntry entry = getEntryByFragmentClass(fragmentClass);
|
||||||
|
if (entry == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return entry.getPreferencesResId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends Fragment> getFragmentClass(@XmlRes final int preferencesResId) {
|
||||||
|
final SettingRegistryEntry entry = getEntryByPreferencesResId(preferencesResId);
|
||||||
|
if (entry == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return entry.getFragmentClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<SettingRegistryEntry> getAllEntries() {
|
||||||
|
return new HashSet<>(registeredEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SettingsResourceRegistry getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class SettingRegistryEntry {
|
||||||
|
@NonNull
|
||||||
|
private final Class<? extends Fragment> fragmentClass;
|
||||||
|
@XmlRes
|
||||||
|
private final int preferencesResId;
|
||||||
|
|
||||||
|
private boolean searchable = true;
|
||||||
|
|
||||||
|
public SettingRegistryEntry(
|
||||||
|
@NonNull final Class<? extends Fragment> fragmentClass,
|
||||||
|
@XmlRes final int preferencesResId
|
||||||
|
) {
|
||||||
|
this.fragmentClass = Objects.requireNonNull(fragmentClass);
|
||||||
|
this.preferencesResId = preferencesResId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("HiddenField")
|
||||||
|
public SettingRegistryEntry setSearchable(final boolean searchable) {
|
||||||
|
this.searchable = searchable;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends Fragment> getFragmentClass() {
|
||||||
|
return fragmentClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPreferencesResId() {
|
||||||
|
return preferencesResId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSearchable() {
|
||||||
|
return searchable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final SettingRegistryEntry that = (SettingRegistryEntry) o;
|
||||||
|
return getPreferencesResId() == that.getPreferencesResId()
|
||||||
|
&& getFragmentClass().equals(that.getFragmentClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(getFragmentClass(), getPreferencesResId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@ public class UpdateSettingsFragment extends BasePreferenceFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||||
addPreferencesFromResource(R.xml.update_settings);
|
addPreferencesFromResourceRegistry();
|
||||||
|
|
||||||
findPreference(getString(R.string.update_app_key))
|
findPreference(getString(R.string.update_app_key))
|
||||||
.setOnPreferenceChangeListener(updatePreferenceChange);
|
.setOnPreferenceChangeListener(updatePreferenceChange);
|
||||||
|
@ -23,7 +23,7 @@ public class VideoAudioSettingsFragment extends BasePreferenceFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||||
addPreferencesFromResource(R.xml.video_audio_settings);
|
addPreferencesFromResourceRegistry();
|
||||||
|
|
||||||
updateSeekOptions();
|
updateSeekOptions();
|
||||||
|
|
||||||
|
@ -0,0 +1,111 @@
|
|||||||
|
package org.schabi.newpipe.settings.preferencesearch;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.apache.commons.text.similarity.FuzzyScore;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class PreferenceFuzzySearchFunction
|
||||||
|
implements PreferenceSearchConfiguration.PreferenceSearchFunction {
|
||||||
|
|
||||||
|
private static final FuzzyScore FUZZY_SCORE = new FuzzyScore(Locale.ROOT);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<PreferenceSearchItem> search(
|
||||||
|
final Stream<PreferenceSearchItem> allAvailable,
|
||||||
|
final String keyword
|
||||||
|
) {
|
||||||
|
final int maxScore = (keyword.length() + 1) * 3 - 2; // First can't get +2 bonus score
|
||||||
|
|
||||||
|
return allAvailable
|
||||||
|
// General search
|
||||||
|
// Check all fields if anyone contains something that kind of matches the keyword
|
||||||
|
.map(item -> new FuzzySearchGeneralDTO(item, keyword))
|
||||||
|
.filter(dto -> dto.getScore() / maxScore >= 0.3f)
|
||||||
|
.map(FuzzySearchGeneralDTO::getItem)
|
||||||
|
// Specific search - Used for determining order of search results
|
||||||
|
// Calculate a score based on specific search fields
|
||||||
|
.map(item -> new FuzzySearchSpecificDTO(item, keyword))
|
||||||
|
.sorted(Comparator.comparing(FuzzySearchSpecificDTO::getScore).reversed())
|
||||||
|
.map(FuzzySearchSpecificDTO::getItem)
|
||||||
|
// Limit the amount of search results
|
||||||
|
.limit(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class FuzzySearchGeneralDTO {
|
||||||
|
private final PreferenceSearchItem item;
|
||||||
|
private final float score;
|
||||||
|
|
||||||
|
FuzzySearchGeneralDTO(
|
||||||
|
final PreferenceSearchItem item,
|
||||||
|
final String keyword) {
|
||||||
|
this.item = item;
|
||||||
|
this.score = FUZZY_SCORE.fuzzyScore(
|
||||||
|
TextUtils.join(";", item.getAllRelevantSearchFields()),
|
||||||
|
keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreferenceSearchItem getItem() {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getScore() {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class FuzzySearchSpecificDTO {
|
||||||
|
private static final Map<Function<PreferenceSearchItem, String>, Float> WEIGHT_MAP = Map.of(
|
||||||
|
// The user will most likely look for the title -> prioritize it
|
||||||
|
PreferenceSearchItem::getTitle, 1.5f,
|
||||||
|
// The summary is also important as it usually contains a larger desc
|
||||||
|
// Example: Searching for '4k' → 'show higher resolution' is shown
|
||||||
|
PreferenceSearchItem::getSummary, 1f,
|
||||||
|
// Entries are also important as they provide all known/possible values
|
||||||
|
// Example: Searching where the resolution can be changed to 720p
|
||||||
|
PreferenceSearchItem::getEntries, 1f
|
||||||
|
);
|
||||||
|
|
||||||
|
private final PreferenceSearchItem item;
|
||||||
|
private final float score;
|
||||||
|
|
||||||
|
FuzzySearchSpecificDTO(
|
||||||
|
final PreferenceSearchItem item,
|
||||||
|
final String keyword) {
|
||||||
|
this.item = item;
|
||||||
|
|
||||||
|
float attributeScoreSum = 0;
|
||||||
|
int countOfAttributesWithScore = 0;
|
||||||
|
for (final Map.Entry<Function<PreferenceSearchItem, String>, Float> we
|
||||||
|
: WEIGHT_MAP.entrySet()) {
|
||||||
|
final String valueToProcess = we.getKey().apply(item);
|
||||||
|
if (valueToProcess.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeScoreSum +=
|
||||||
|
FUZZY_SCORE.fuzzyScore(valueToProcess, keyword) * we.getValue();
|
||||||
|
countOfAttributesWithScore++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (countOfAttributesWithScore != 0) {
|
||||||
|
this.score = attributeScoreSum / countOfAttributesWithScore;
|
||||||
|
} else {
|
||||||
|
this.score = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreferenceSearchItem getItem() {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getScore() {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,199 @@
|
|||||||
|
package org.schabi.newpipe.settings.preferencesearch;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.XmlRes;
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the corresponding preference-file(s).
|
||||||
|
*/
|
||||||
|
public class PreferenceParser {
|
||||||
|
private static final String TAG = "PreferenceParser";
|
||||||
|
|
||||||
|
private static final String NS_ANDROID = "http://schemas.android.com/apk/res/android";
|
||||||
|
private static final String NS_SEARCH = "http://schemas.android.com/apk/preferencesearch";
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final Map<String, ?> allPreferences;
|
||||||
|
private final PreferenceSearchConfiguration searchConfiguration;
|
||||||
|
|
||||||
|
public PreferenceParser(
|
||||||
|
final Context context,
|
||||||
|
final PreferenceSearchConfiguration searchConfiguration
|
||||||
|
) {
|
||||||
|
this.context = context;
|
||||||
|
this.allPreferences = PreferenceManager.getDefaultSharedPreferences(context).getAll();
|
||||||
|
this.searchConfiguration = searchConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PreferenceSearchItem> parse(
|
||||||
|
@XmlRes final int resId
|
||||||
|
) {
|
||||||
|
final List<PreferenceSearchItem> results = new ArrayList<>();
|
||||||
|
final XmlPullParser xpp = context.getResources().getXml(resId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
xpp.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
|
||||||
|
xpp.setFeature(XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES, true);
|
||||||
|
|
||||||
|
final List<String> breadcrumbs = new ArrayList<>();
|
||||||
|
while (xpp.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||||
|
if (xpp.getEventType() == XmlPullParser.START_TAG) {
|
||||||
|
final PreferenceSearchItem result = parseSearchResult(
|
||||||
|
xpp,
|
||||||
|
joinBreadcrumbs(breadcrumbs),
|
||||||
|
resId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!searchConfiguration.getParserIgnoreElements().contains(xpp.getName())
|
||||||
|
&& result.hasData()
|
||||||
|
&& !"true".equals(getAttribute(xpp, NS_SEARCH, "ignore"))) {
|
||||||
|
results.add(result);
|
||||||
|
}
|
||||||
|
if (searchConfiguration.getParserContainerElements().contains(xpp.getName())) {
|
||||||
|
// This code adds breadcrumbs for certain containers (e.g. PreferenceScreen)
|
||||||
|
// Example: Video and Audio > Player
|
||||||
|
breadcrumbs.add(result.getTitle() == null ? "" : result.getTitle());
|
||||||
|
}
|
||||||
|
} else if (xpp.getEventType() == XmlPullParser.END_TAG
|
||||||
|
&& searchConfiguration.getParserContainerElements()
|
||||||
|
.contains(xpp.getName())) {
|
||||||
|
breadcrumbs.remove(breadcrumbs.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
xpp.next();
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.w(TAG, "Failed to parse resid=" + resId, e);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String joinBreadcrumbs(final List<String> breadcrumbs) {
|
||||||
|
return breadcrumbs.stream()
|
||||||
|
.filter(crumb -> !TextUtils.isEmpty(crumb))
|
||||||
|
.collect(Collectors.joining(" > "));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAttribute(
|
||||||
|
final XmlPullParser xpp,
|
||||||
|
@NonNull final String attribute
|
||||||
|
) {
|
||||||
|
final String nsSearchAttr = getAttribute(xpp, NS_SEARCH, attribute);
|
||||||
|
if (nsSearchAttr != null) {
|
||||||
|
return nsSearchAttr;
|
||||||
|
}
|
||||||
|
return getAttribute(xpp, NS_ANDROID, attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAttribute(
|
||||||
|
final XmlPullParser xpp,
|
||||||
|
@NonNull final String namespace,
|
||||||
|
@NonNull final String attribute
|
||||||
|
) {
|
||||||
|
return xpp.getAttributeValue(namespace, attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PreferenceSearchItem parseSearchResult(
|
||||||
|
final XmlPullParser xpp,
|
||||||
|
final String breadcrumbs,
|
||||||
|
@XmlRes final int searchIndexItemResId
|
||||||
|
) {
|
||||||
|
final String key = readString(getAttribute(xpp, "key"));
|
||||||
|
final String[] entries = readStringArray(getAttribute(xpp, "entries"));
|
||||||
|
final String[] entryValues = readStringArray(getAttribute(xpp, "entryValues"));
|
||||||
|
|
||||||
|
return new PreferenceSearchItem(
|
||||||
|
key,
|
||||||
|
tryFillInPreferenceValue(
|
||||||
|
readString(getAttribute(xpp, "title")),
|
||||||
|
key,
|
||||||
|
entries,
|
||||||
|
entryValues),
|
||||||
|
tryFillInPreferenceValue(
|
||||||
|
readString(getAttribute(xpp, "summary")),
|
||||||
|
key,
|
||||||
|
entries,
|
||||||
|
entryValues),
|
||||||
|
TextUtils.join(",", entries),
|
||||||
|
breadcrumbs,
|
||||||
|
searchIndexItemResId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] readStringArray(@Nullable final String s) {
|
||||||
|
if (s == null) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
if (s.startsWith("@")) {
|
||||||
|
try {
|
||||||
|
return context.getResources().getStringArray(Integer.parseInt(s.substring(1)));
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.w(TAG, "Unable to readStringArray from '" + s + "'", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readString(@Nullable final String s) {
|
||||||
|
if (s == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (s.startsWith("@")) {
|
||||||
|
try {
|
||||||
|
return context.getString(Integer.parseInt(s.substring(1)));
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.w(TAG, "Unable to readString from '" + s + "'", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String tryFillInPreferenceValue(
|
||||||
|
@Nullable final String s,
|
||||||
|
@Nullable final String key,
|
||||||
|
final String[] entries,
|
||||||
|
final String[] entryValues
|
||||||
|
) {
|
||||||
|
if (s == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (key == null) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve value
|
||||||
|
Object prefValue = allPreferences.get(key);
|
||||||
|
if (prefValue == null) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Resolve ListPreference values
|
||||||
|
*
|
||||||
|
* entryValues = Values/Keys that are saved
|
||||||
|
* entries = Actual human readable names
|
||||||
|
*/
|
||||||
|
if (entries.length > 0 && entryValues.length == entries.length) {
|
||||||
|
final int entryIndex = Arrays.asList(entryValues).indexOf(prefValue);
|
||||||
|
if (entryIndex != -1) {
|
||||||
|
prefValue = entries[entryIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return String.format(s, prefValue.toString());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package org.schabi.newpipe.settings.preferencesearch;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.databinding.SettingsPreferencesearchListItemResultBinding;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
class PreferenceSearchAdapter
|
||||||
|
extends RecyclerView.Adapter<PreferenceSearchAdapter.PreferenceViewHolder> {
|
||||||
|
private List<PreferenceSearchItem> dataset = new ArrayList<>();
|
||||||
|
private Consumer<PreferenceSearchItem> onItemClickListener;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public PreferenceViewHolder onCreateViewHolder(
|
||||||
|
@NonNull final ViewGroup parent,
|
||||||
|
final int viewType
|
||||||
|
) {
|
||||||
|
return new PreferenceViewHolder(
|
||||||
|
SettingsPreferencesearchListItemResultBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.getContext()),
|
||||||
|
parent,
|
||||||
|
false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(
|
||||||
|
@NonNull final PreferenceViewHolder holder,
|
||||||
|
final int position
|
||||||
|
) {
|
||||||
|
final PreferenceSearchItem item = dataset.get(position);
|
||||||
|
|
||||||
|
holder.binding.title.setText(item.getTitle());
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(item.getSummary())) {
|
||||||
|
holder.binding.summary.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
holder.binding.summary.setVisibility(View.VISIBLE);
|
||||||
|
holder.binding.summary.setText(item.getSummary());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(item.getBreadcrumbs())) {
|
||||||
|
holder.binding.breadcrumbs.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
holder.binding.breadcrumbs.setVisibility(View.VISIBLE);
|
||||||
|
holder.binding.breadcrumbs.setText(item.getBreadcrumbs());
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener(v -> {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.accept(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void setContent(final List<PreferenceSearchItem> items) {
|
||||||
|
dataset = new ArrayList<>(items);
|
||||||
|
this.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return dataset.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOnItemClickListener(final Consumer<PreferenceSearchItem> onItemClickListener) {
|
||||||
|
this.onItemClickListener = onItemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PreferenceViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
final SettingsPreferencesearchListItemResultBinding binding;
|
||||||
|
|
||||||
|
PreferenceViewHolder(final SettingsPreferencesearchListItemResultBinding binding) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package org.schabi.newpipe.settings.preferencesearch;
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceCategory;
|
||||||
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class PreferenceSearchConfiguration {
|
||||||
|
private PreferenceSearchFunction searcher = new PreferenceFuzzySearchFunction();
|
||||||
|
|
||||||
|
private final List<String> parserIgnoreElements = Arrays.asList(
|
||||||
|
PreferenceCategory.class.getSimpleName());
|
||||||
|
private final List<String> parserContainerElements = Arrays.asList(
|
||||||
|
PreferenceCategory.class.getSimpleName(),
|
||||||
|
PreferenceScreen.class.getSimpleName());
|
||||||
|
|
||||||
|
|
||||||
|
public void setSearcher(final PreferenceSearchFunction searcher) {
|
||||||
|
this.searcher = Objects.requireNonNull(searcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreferenceSearchFunction getSearcher() {
|
||||||
|
return searcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getParserIgnoreElements() {
|
||||||
|
return parserIgnoreElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getParserContainerElements() {
|
||||||
|
return parserContainerElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface PreferenceSearchFunction {
|
||||||
|
Stream<PreferenceSearchItem> search(
|
||||||
|
Stream<PreferenceSearchItem> allAvailable,
|
||||||
|
String keyword);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package org.schabi.newpipe.settings.preferencesearch;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.databinding.SettingsPreferencesearchFragmentBinding;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the search results.
|
||||||
|
*/
|
||||||
|
public class PreferenceSearchFragment extends Fragment {
|
||||||
|
public static final String NAME = PreferenceSearchFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
private PreferenceSearcher searcher;
|
||||||
|
|
||||||
|
private SettingsPreferencesearchFragmentBinding binding;
|
||||||
|
private PreferenceSearchAdapter adapter;
|
||||||
|
|
||||||
|
public void setSearcher(final PreferenceSearcher searcher) {
|
||||||
|
this.searcher = searcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(
|
||||||
|
@NonNull final LayoutInflater inflater,
|
||||||
|
@Nullable final ViewGroup container,
|
||||||
|
@Nullable final Bundle savedInstanceState
|
||||||
|
) {
|
||||||
|
binding = SettingsPreferencesearchFragmentBinding.inflate(inflater, container, false);
|
||||||
|
|
||||||
|
binding.searchResults.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
|
||||||
|
adapter = new PreferenceSearchAdapter();
|
||||||
|
adapter.setOnItemClickListener(this::onItemClicked);
|
||||||
|
binding.searchResults.setAdapter(adapter);
|
||||||
|
|
||||||
|
return binding.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateSearchResults(final String keyword) {
|
||||||
|
if (adapter == null || searcher == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<PreferenceSearchItem> results =
|
||||||
|
!TextUtils.isEmpty(keyword)
|
||||||
|
? searcher.searchFor(keyword)
|
||||||
|
: new ArrayList<>();
|
||||||
|
|
||||||
|
adapter.setContent(new ArrayList<>(results));
|
||||||
|
|
||||||
|
setEmptyViewShown(results.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setEmptyViewShown(final boolean shown) {
|
||||||
|
binding.emptyStateView.setVisibility(shown ? View.VISIBLE : View.GONE);
|
||||||
|
binding.searchResults.setVisibility(shown ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onItemClicked(final PreferenceSearchItem item) {
|
||||||
|
if (!(getActivity() instanceof PreferenceSearchResultListener)) {
|
||||||
|
throw new ClassCastException(
|
||||||
|
getActivity().toString() + " must implement SearchPreferenceResultListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
((PreferenceSearchResultListener) getActivity()).onSearchResultClicked(item);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
package org.schabi.newpipe.settings.preferencesearch;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.XmlRes;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a preference-item inside the search.
|
||||||
|
*/
|
||||||
|
public class PreferenceSearchItem {
|
||||||
|
/**
|
||||||
|
* Key of the setting/preference. E.g. used inside {@link android.content.SharedPreferences}.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private final String key;
|
||||||
|
/**
|
||||||
|
* Title of the setting, e.g. 'Default resolution' or 'Show higher resolutions'.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private final String title;
|
||||||
|
/**
|
||||||
|
* Summary of the setting, e.g. '480p' or 'Only some devices can play 2k/4k'.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private final String summary;
|
||||||
|
/**
|
||||||
|
* Possible entries of the setting, e.g. 480p,720p,...
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private final String entries;
|
||||||
|
/**
|
||||||
|
* Breadcrumbs - a hint where the setting is located e.g. 'Video and Audio > Player'
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private final String breadcrumbs;
|
||||||
|
/**
|
||||||
|
* The xml-resource where this item was found/built from.
|
||||||
|
*/
|
||||||
|
@XmlRes
|
||||||
|
private final int searchIndexItemResId;
|
||||||
|
|
||||||
|
public PreferenceSearchItem(
|
||||||
|
@NonNull final String key,
|
||||||
|
@NonNull final String title,
|
||||||
|
@NonNull final String summary,
|
||||||
|
@NonNull final String entries,
|
||||||
|
@NonNull final String breadcrumbs,
|
||||||
|
@XmlRes final int searchIndexItemResId
|
||||||
|
) {
|
||||||
|
this.key = Objects.requireNonNull(key);
|
||||||
|
this.title = Objects.requireNonNull(title);
|
||||||
|
this.summary = Objects.requireNonNull(summary);
|
||||||
|
this.entries = Objects.requireNonNull(entries);
|
||||||
|
this.breadcrumbs = Objects.requireNonNull(breadcrumbs);
|
||||||
|
this.searchIndexItemResId = searchIndexItemResId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEntries() {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBreadcrumbs() {
|
||||||
|
return breadcrumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSearchIndexItemResId() {
|
||||||
|
return searchIndexItemResId;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasData() {
|
||||||
|
return !key.isEmpty() && !title.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAllRelevantSearchFields() {
|
||||||
|
return Arrays.asList(
|
||||||
|
getTitle(),
|
||||||
|
getSummary(),
|
||||||
|
getEntries(),
|
||||||
|
getBreadcrumbs());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PreferenceItem: " + title + " " + summary + " " + key;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
package org.schabi.newpipe.settings.preferencesearch;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.PorterDuffColorFilter;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.RippleDrawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
|
import androidx.preference.Preference;
|
||||||
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
import androidx.preference.PreferenceGroup;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
|
|
||||||
|
public final class PreferenceSearchResultHighlighter {
|
||||||
|
private static final String TAG = "PrefSearchResHighlter";
|
||||||
|
|
||||||
|
private PreferenceSearchResultHighlighter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight the specified preference.
|
||||||
|
* <br/>
|
||||||
|
* Note: This function is Thread independent (can be called from outside of the main thread).
|
||||||
|
*
|
||||||
|
* @param item The item to highlight
|
||||||
|
* @param prefsFragment The fragment where the items is located on
|
||||||
|
*/
|
||||||
|
public static void highlight(
|
||||||
|
final PreferenceSearchItem item,
|
||||||
|
final PreferenceFragmentCompat prefsFragment
|
||||||
|
) {
|
||||||
|
new Handler(Looper.getMainLooper()).post(() -> doHighlight(item, prefsFragment));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void doHighlight(
|
||||||
|
final PreferenceSearchItem item,
|
||||||
|
final PreferenceFragmentCompat prefsFragment
|
||||||
|
) {
|
||||||
|
final Preference prefResult = prefsFragment.findPreference(item.getKey());
|
||||||
|
|
||||||
|
if (prefResult == null) {
|
||||||
|
Log.w(TAG, "Preference '" + item.getKey() + "' not found on '" + prefsFragment + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final RecyclerView recyclerView = prefsFragment.getListView();
|
||||||
|
final RecyclerView.Adapter<?> adapter = recyclerView.getAdapter();
|
||||||
|
if (adapter instanceof PreferenceGroup.PreferencePositionCallback) {
|
||||||
|
final int position = ((PreferenceGroup.PreferencePositionCallback) adapter)
|
||||||
|
.getPreferenceAdapterPosition(prefResult);
|
||||||
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
|
recyclerView.scrollToPosition(position);
|
||||||
|
recyclerView.postDelayed(() -> {
|
||||||
|
final RecyclerView.ViewHolder holder =
|
||||||
|
recyclerView.findViewHolderForAdapterPosition(position);
|
||||||
|
if (holder != null) {
|
||||||
|
final Drawable background = holder.itemView.getBackground();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
|
||||||
|
&& background instanceof RippleDrawable) {
|
||||||
|
showRippleAnimation((RippleDrawable) background);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
highlightFallback(prefsFragment, prefResult);
|
||||||
|
}, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
highlightFallback(prefsFragment, prefResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative highlighting (shows an → arrow in front of the setting)if ripple does not work.
|
||||||
|
*
|
||||||
|
* @param prefsFragment
|
||||||
|
* @param prefResult
|
||||||
|
*/
|
||||||
|
private static void highlightFallback(
|
||||||
|
final PreferenceFragmentCompat prefsFragment,
|
||||||
|
final Preference prefResult
|
||||||
|
) {
|
||||||
|
// Get primary color from text for highlight icon
|
||||||
|
final TypedValue typedValue = new TypedValue();
|
||||||
|
final Resources.Theme theme = prefsFragment.getActivity().getTheme();
|
||||||
|
theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
|
||||||
|
final TypedArray arr = prefsFragment.getActivity()
|
||||||
|
.obtainStyledAttributes(
|
||||||
|
typedValue.data,
|
||||||
|
new int[]{android.R.attr.textColorPrimary});
|
||||||
|
final int color = arr.getColor(0, 0xffE53935);
|
||||||
|
arr.recycle();
|
||||||
|
|
||||||
|
// Show highlight icon
|
||||||
|
final Drawable oldIcon = prefResult.getIcon();
|
||||||
|
final boolean oldSpaceReserved = prefResult.isIconSpaceReserved();
|
||||||
|
final Drawable highlightIcon =
|
||||||
|
AppCompatResources.getDrawable(
|
||||||
|
prefsFragment.requireContext(),
|
||||||
|
R.drawable.ic_play_arrow);
|
||||||
|
highlightIcon.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
|
||||||
|
prefResult.setIcon(highlightIcon);
|
||||||
|
|
||||||
|
prefsFragment.scrollToPreference(prefResult);
|
||||||
|
|
||||||
|
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||||
|
prefResult.setIcon(oldIcon);
|
||||||
|
prefResult.setIconSpaceReserved(oldSpaceReserved);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showRippleAnimation(final RippleDrawable rippleDrawable) {
|
||||||
|
rippleDrawable.setState(
|
||||||
|
new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled});
|
||||||
|
new Handler(Looper.getMainLooper())
|
||||||
|
.postDelayed(() -> rippleDrawable.setState(new int[]{}), 1000);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package org.schabi.newpipe.settings.preferencesearch;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public interface PreferenceSearchResultListener {
|
||||||
|
void onSearchResultClicked(@NonNull PreferenceSearchItem result);
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package org.schabi.newpipe.settings.preferencesearch;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class PreferenceSearcher {
|
||||||
|
private final List<PreferenceSearchItem> allEntries = new ArrayList<>();
|
||||||
|
|
||||||
|
private final PreferenceSearchConfiguration configuration;
|
||||||
|
|
||||||
|
public PreferenceSearcher(final PreferenceSearchConfiguration configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(final List<PreferenceSearchItem> items) {
|
||||||
|
allEntries.addAll(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PreferenceSearchItem> searchFor(final String keyword) {
|
||||||
|
if (TextUtils.isEmpty(keyword)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuration.getSearcher()
|
||||||
|
.search(allEntries.stream(), keyword)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
allEntries.clear();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Contains classes for searching inside the preferences.
|
||||||
|
* <br/>
|
||||||
|
* This code is based on
|
||||||
|
* <a href="https://github.com/ByteHamster/SearchPreference">ByteHamster/SearchPreference</a>
|
||||||
|
* (MIT license) but was heavily modified/refactored for our use.
|
||||||
|
*
|
||||||
|
* @author litetex
|
||||||
|
*/
|
||||||
|
package org.schabi.newpipe.settings.preferencesearch;
|
@ -44,8 +44,6 @@ import java.util.List;
|
|||||||
import static org.schabi.newpipe.settings.tabs.Tab.typeFrom;
|
import static org.schabi.newpipe.settings.tabs.Tab.typeFrom;
|
||||||
|
|
||||||
public class ChooseTabsFragment extends Fragment {
|
public class ChooseTabsFragment extends Fragment {
|
||||||
private static final int MENU_ITEM_RESTORE_ID = 123456;
|
|
||||||
|
|
||||||
private TabsManager tabsManager;
|
private TabsManager tabsManager;
|
||||||
|
|
||||||
private final List<Tab> tabList = new ArrayList<>();
|
private final List<Tab> tabList = new ArrayList<>();
|
||||||
@ -110,21 +108,14 @@ public class ChooseTabsFragment extends Fragment {
|
|||||||
@NonNull final MenuInflater inflater) {
|
@NonNull final MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
|
||||||
final MenuItem restoreItem = menu.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE,
|
final MenuItem restoreItem = menu.add(R.string.restore_defaults);
|
||||||
R.string.restore_defaults);
|
|
||||||
restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(),
|
restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(),
|
||||||
R.drawable.ic_settings_backup_restore));
|
R.drawable.ic_settings_backup_restore));
|
||||||
}
|
restoreItem.setOnMenuItemClickListener(ev -> {
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
|
||||||
if (item.getItemId() == MENU_ITEM_RESTORE_ID) {
|
|
||||||
restoreDefaults();
|
restoreDefaults();
|
||||||
return true;
|
return true;
|
||||||
}
|
});
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
43
app/src/main/java/org/schabi/newpipe/util/KeyboardUtil.java
Normal file
43
app/src/main/java/org/schabi/newpipe/util/KeyboardUtil.java
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package org.schabi.newpipe.util;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for the Android keyboard.
|
||||||
|
* <p>
|
||||||
|
* See also <a href="https://stackoverflow.com/q/1109022">https://stackoverflow.com/q/1109022</a>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class KeyboardUtil {
|
||||||
|
private KeyboardUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showKeyboard(final Activity activity, final EditText editText) {
|
||||||
|
if (activity == null || editText == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editText.requestFocus()) {
|
||||||
|
final InputMethodManager imm = ContextCompat.getSystemService(activity,
|
||||||
|
InputMethodManager.class);
|
||||||
|
imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hideKeyboard(final Activity activity, final EditText editText) {
|
||||||
|
if (activity == null || editText == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final InputMethodManager imm = ContextCompat.getSystemService(activity,
|
||||||
|
InputMethodManager.class);
|
||||||
|
imm.hideSoftInputFromWindow(editText.getWindowToken(),
|
||||||
|
InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
||||||
|
|
||||||
|
editText.clearFocus();
|
||||||
|
}
|
||||||
|
}
|
@ -6,14 +6,14 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
tools:context="org.schabi.newpipe.MainActivity">
|
tools:context="org.schabi.newpipe.MainActivity">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/settings_toolbar_layout"
|
||||||
|
layout="@layout/toolbar_layout" />
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/settings_fragment_holder"
|
android:id="@+id/settings_fragment_holder"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="?attr/actionBarSize" />
|
android:layout_marginTop="?attr/actionBarSize" />
|
||||||
|
|
||||||
<include
|
|
||||||
layout="@layout/toolbar_layout"
|
|
||||||
android:id="@+id/settings_toolbar_layout"/>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="?android:attr/windowBackground">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:background="?attr/toolbar_shadow" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/empty_state_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="gone">
|
||||||
|
|
||||||
|
<org.schabi.newpipe.views.NewPipeTextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:text="╰(°●°╰)"
|
||||||
|
android:textSize="35sp"
|
||||||
|
tools:ignore="HardcodedText,UnusedAttribute" />
|
||||||
|
|
||||||
|
<org.schabi.newpipe.views.NewPipeTextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:text="@string/search_no_results"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/searchResults"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:scrollbars="vertical" />
|
||||||
|
</LinearLayout>
|
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?android:attr/editTextColor"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="Title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/summary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?android:attr/editTextColor"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="Summary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/breadcrumbs"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:alpha="0.6"
|
||||||
|
android:textColor="?android:attr/editTextColor"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="Breadcrumb" />
|
||||||
|
</LinearLayout>
|
11
app/src/main/res/menu/menu_settings_main_fragment.xml
Normal file
11
app/src/main/res/menu/menu_settings_main_fragment.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_search"
|
||||||
|
android:icon="@drawable/ic_search"
|
||||||
|
android:orderInCategory="1"
|
||||||
|
android:title="@string/search"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
</menu>
|
@ -441,6 +441,7 @@
|
|||||||
<string name="caption_setting_title">Captions</string>
|
<string name="caption_setting_title">Captions</string>
|
||||||
<string name="caption_setting_description">Modify player caption text scale and background styles. Requires app restart to take effect</string>
|
<string name="caption_setting_description">Modify player caption text scale and background styles. Requires app restart to take effect</string>
|
||||||
<!-- Debug Settings -->
|
<!-- Debug Settings -->
|
||||||
|
<string name="leak_canary_not_available">LeakCanary is not available</string>
|
||||||
<string name="enable_leak_canary_summary">Memory leak monitoring may cause the app to become unresponsive when heap dumping</string>
|
<string name="enable_leak_canary_summary">Memory leak monitoring may cause the app to become unresponsive when heap dumping</string>
|
||||||
<string name="show_memory_leaks">Show memory leaks</string>
|
<string name="show_memory_leaks">Show memory leaks</string>
|
||||||
<string name="enable_disposed_exceptions_title">Report out-of-lifecycle errors</string>
|
<string name="enable_disposed_exceptions_title">Report out-of-lifecycle errors</string>
|
||||||
|
@ -128,13 +128,6 @@
|
|||||||
app:singleLineTitle="false"
|
app:singleLineTitle="false"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
<Preference
|
|
||||||
android:key="@string/clear_cookie_key"
|
|
||||||
android:summary="@string/clear_cookie_summary"
|
|
||||||
android:title="@string/clear_cookie_title"
|
|
||||||
app:singleLineTitle="false"
|
|
||||||
app:iconSpaceReserved="false" />
|
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:layout="@layout/settings_category_header_layout"
|
android:layout="@layout/settings_category_header_layout"
|
||||||
android:title="@string/settings_category_feed_title"
|
android:title="@string/settings_category_feed_title"
|
||||||
|
@ -71,6 +71,13 @@
|
|||||||
app:singleLineTitle="false"
|
app:singleLineTitle="false"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="@string/clear_cookie_key"
|
||||||
|
android:summary="@string/clear_cookie_summary"
|
||||||
|
android:title="@string/clear_cookie_title"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:key="general_preferences"
|
|
||||||
android:title="@string/settings_category_updates_title">
|
android:title="@string/settings_category_updates_title">
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:key="general_preferences"
|
|
||||||
android:title="@string/settings">
|
|
||||||
|
|
||||||
<PreferenceScreen
|
|
||||||
android:fragment="org.schabi.newpipe.settings.VideoAudioSettingsFragment"
|
|
||||||
android:icon="@drawable/ic_headset"
|
|
||||||
android:title="@string/settings_category_video_audio_title"
|
|
||||||
app:iconSpaceReserved="false" />
|
|
||||||
|
|
||||||
<PreferenceScreen
|
|
||||||
android:fragment="org.schabi.newpipe.settings.DownloadSettingsFragment"
|
|
||||||
android:icon="@drawable/ic_file_download"
|
|
||||||
android:title="@string/settings_category_downloads_title"
|
|
||||||
app:iconSpaceReserved="false" />
|
|
||||||
|
|
||||||
<PreferenceScreen
|
|
||||||
android:fragment="org.schabi.newpipe.settings.AppearanceSettingsFragment"
|
|
||||||
android:icon="@drawable/ic_palette"
|
|
||||||
android:title="@string/settings_category_appearance_title"
|
|
||||||
app:iconSpaceReserved="false" />
|
|
||||||
|
|
||||||
<PreferenceScreen
|
|
||||||
android:fragment="org.schabi.newpipe.settings.HistorySettingsFragment"
|
|
||||||
android:icon="@drawable/ic_history"
|
|
||||||
android:title="@string/settings_category_history_title"
|
|
||||||
app:iconSpaceReserved="false" />
|
|
||||||
|
|
||||||
<PreferenceScreen
|
|
||||||
android:fragment="org.schabi.newpipe.settings.ContentSettingsFragment"
|
|
||||||
android:icon="@drawable/ic_language"
|
|
||||||
android:title="@string/content"
|
|
||||||
app:iconSpaceReserved="false" />
|
|
||||||
|
|
||||||
<PreferenceScreen
|
|
||||||
android:fragment="org.schabi.newpipe.settings.NotificationSettingsFragment"
|
|
||||||
android:icon="@drawable/ic_play_arrow"
|
|
||||||
android:title="@string/settings_category_notification_title"
|
|
||||||
app:iconSpaceReserved="false" />
|
|
||||||
|
|
||||||
<PreferenceScreen
|
|
||||||
android:fragment="org.schabi.newpipe.settings.UpdateSettingsFragment"
|
|
||||||
android:icon="@drawable/ic_cloud_download"
|
|
||||||
android:key="update_pref_screen_key"
|
|
||||||
android:title="@string/settings_category_updates_title"
|
|
||||||
app:iconSpaceReserved="false" />
|
|
||||||
</PreferenceScreen>
|
|
Loading…
Reference in New Issue
Block a user