From 07fb319e886f8bee02a4d1de96ecd2185999efa0 Mon Sep 17 00:00:00 2001
From: litetex <40789489+litetex@users.noreply.github.com>
Date: Fri, 24 Dec 2021 21:35:15 +0100
Subject: [PATCH] Applied code changes for preference search framework
---
.../settings/AppearanceSettingsFragment.java | 2 +-
.../settings/BasePreferenceFragment.java | 5 +
.../settings/ContentSettingsFragment.java | 8 +-
.../settings/DownloadSettingsFragment.java | 2 +-
.../settings/HistorySettingsFragment.java | 2 +-
.../settings/MainSettingsFragment.java | 43 +++-
.../settings/NotificationSettingsFragment.kt | 2 +-
.../newpipe/settings/SettingsActivity.java | 206 +++++++++++++++++-
.../settings/SettingsResourceRegistry.java | 151 +++++++++++++
.../settings/UpdateSettingsFragment.java | 2 +-
.../settings/VideoAudioSettingsFragment.java | 2 +-
11 files changed, 408 insertions(+), 17 deletions(-)
create mode 100644 app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
diff --git a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
index 4bc5a210e..e08562908 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/AppearanceSettingsFragment.java
@@ -17,7 +17,7 @@ public class AppearanceSettingsFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
- addPreferencesFromResource(R.xml.appearance_settings);
+ addPreferencesFromResourceRegistry();
final String themeKey = getString(R.string.theme_key);
// the key of the active theme when settings were opened (or recreated after theme change)
diff --git a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java
index a745861ad..619579f3a 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/BasePreferenceFragment.java
@@ -28,6 +28,11 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
super.onCreate(savedInstanceState);
}
+ protected void addPreferencesFromResourceRegistry() {
+ addPreferencesFromResource(
+ SettingsResourceRegistry.getInstance().getPreferencesResId(this.getClass()));
+ }
+
@Override
public void onViewCreated(@NonNull final View rootView,
@Nullable final Bundle savedInstanceState) {
diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
index 1c8eb5cd2..d79ea0cd5 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -1,5 +1,8 @@
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.content.Context;
import android.content.Intent;
@@ -38,9 +41,6 @@ import java.util.Date;
import java.util.Locale;
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 {
private static final String ZIP_MIME_TYPE = "application/zip";
@@ -70,7 +70,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
importExportDataPathKey = getString(R.string.import_export_data_path);
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
- addPreferencesFromResource(R.xml.content_settings);
+ addPreferencesFromResourceRegistry();
final Preference importDataPreference = requirePreference(R.string.import_data);
importDataPreference.setOnPreferenceClickListener((Preference p) -> {
diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java
index 681aee409..fe327e1b5 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java
@@ -54,7 +54,7 @@ public class DownloadSettingsFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
- addPreferencesFromResource(R.xml.download_settings);
+ addPreferencesFromResourceRegistry();
downloadPathVideoPreference = getString(R.string.download_path_video_key);
downloadPathAudioPreference = getString(R.string.download_path_audio_key);
diff --git a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java
index 33e0ba16b..868618110 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java
@@ -29,7 +29,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
- addPreferencesFromResource(R.xml.history_settings);
+ addPreferencesFromResourceRegistry();
cacheWipeKey = getString(R.string.metadata_cache_wipe_key);
viewsHistoryClearKey = getString(R.string.clear_views_history_key);
diff --git a/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java
index 12599b828..6cd165861 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/MainSettingsFragment.java
@@ -1,7 +1,11 @@
package org.schabi.newpipe.settings;
import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import androidx.annotation.NonNull;
import androidx.preference.Preference;
import org.schabi.newpipe.App;
@@ -12,10 +16,15 @@ import org.schabi.newpipe.R;
public class MainSettingsFragment extends BasePreferenceFragment {
public static final boolean DEBUG = MainActivity.DEBUG;
+ private SettingsActivity settingsActivity;
+
@Override
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())) {
final Preference update
= findPreference(getString(R.string.update_pref_screen_key));
@@ -24,4 +33,36 @@ public class MainSettingsFragment extends BasePreferenceFragment {
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), false).apply();
}
}
+
+ @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();
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.kt b/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.kt
index e03aa4074..6bea8b69e 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.kt
+++ b/app/src/main/java/org/schabi/newpipe/settings/NotificationSettingsFragment.kt
@@ -7,7 +7,7 @@ import org.schabi.newpipe.R
class NotificationSettingsFragment : BasePreferenceFragment() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- addPreferencesFromResource(R.xml.notification_settings)
+ addPreferencesFromResourceRegistry()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
val colorizePref: Preference? = findPreference(getString(R.string.notification_colorize_key))
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
index 02e2538c5..787740cc2 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
@@ -1,22 +1,42 @@
package org.schabi.newpipe.settings;
+import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
+
import android.os.Bundle;
+import android.util.Log;
import android.view.Menu;
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.AppCompatActivity;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
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.databinding.SettingsLayoutBinding;
+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.util.DeviceUtils;
+import org.schabi.newpipe.util.KeyboardUtil;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.views.FocusOverlayView;
-import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
+import java.util.concurrent.TimeUnit;
/*
* Created by Christian Schabesberger on 31.08.15.
@@ -39,7 +59,23 @@ import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
*/
public class SettingsActivity extends AppCompatActivity
- implements BasePreferenceFragment.OnPreferenceStartFragmentCallback {
+ implements
+ BasePreferenceFragment.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;
+
@Override
protected void onCreate(final Bundle savedInstanceBundle) {
setTheme(ThemeHelper.getSettingsThemeStyle(this));
@@ -49,6 +85,7 @@ public class SettingsActivity extends AppCompatActivity
final SettingsLayoutBinding settingsLayoutBinding =
SettingsLayoutBinding.inflate(getLayoutInflater());
setContentView(settingsLayoutBinding.getRoot());
+ initSearch(settingsLayoutBinding);
setSupportActionBar(settingsLayoutBinding.settingsToolbarLayout.toolbar);
@@ -78,6 +115,12 @@ public class SettingsActivity extends AppCompatActivity
public boolean onOptionsItemSelected(final MenuItem item) {
final int id = item.getItemId();
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) {
finish();
} else {
@@ -91,14 +134,165 @@ public class SettingsActivity extends AppCompatActivity
@Override
public boolean onPreferenceStartFragment(final PreferenceFragmentCompat caller,
final Preference preference) {
- final Fragment fragment = Fragment
- .instantiate(this, preference.getFragment(), preference.getExtras());
+ showSettingsFragment(instantiateFragment(preference.getFragment()));
+ return true;
+ }
+
+ private Fragment instantiateFragment(@NonNull final String className) {
+ return getSupportFragmentManager()
+ .getFragmentFactory()
+ .instantiate(this.getClassLoader(), className);
+ }
+
+ private void showSettingsFragment(final Fragment fragment) {
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(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)
.commit();
- return true;
}
+
+ @Override
+ protected void onDestroy() {
+ setMenuSearchItem(null);
+ super.onDestroy();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Search
+ //////////////////////////////////////////////////////////////////////////*/
+ //region Search
+
+ private void initSearch(final SettingsLayoutBinding settingsLayoutBinding) {
+ 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(() -> onSearchChanged()));
+
+ // Configure clear button
+ searchContainer.findViewById(R.id.toolbar_search_clear)
+ .setOnClickListener(ev -> resetSearchText());
+
+ // Build search configuration using SettingsResourceRegistry
+ prepareSearchConfig();
+
+ final PreferenceSearchConfiguration config = new PreferenceSearchConfiguration();
+ SettingsResourceRegistry.getInstance().getAllEntries().stream()
+ .filter(SettingsResourceRegistry.SettingRegistryEntry::isSearchable)
+ .map(SettingsResourceRegistry.SettingRegistryEntry::getPreferencesResId)
+ .forEach(config::index);
+
+ searchFragment = new PreferenceSearchFragment(config);
+ }
+
+ private void prepareSearchConfig() {
+ // Check if the update settings should be available
+ if (!CheckForNewAppVersion.isReleaseApk(App.getApp())) {
+ SettingsResourceRegistry.getInstance()
+ .getEntryByPreferencesResId(R.xml.update_settings)
+ .setSearchable(false);
+ }
+ }
+
+ public void setMenuSearchItem(final MenuItem menuSearchItem) {
+ this.menuSearchItem = menuSearchItem;
+ }
+
+ public void setSearchActive(final boolean active) {
+ // Ignore if search is already in correct state
+ if (isSearchActive() == active) {
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "setSearchActive called active=" + active);
+ }
+
+ searchContainer.setVisibility(active ? View.VISIBLE : View.GONE);
+ if (menuSearchItem != null) {
+ menuSearchItem.setVisible(!active);
+ }
+
+ final FragmentManager fm = getSupportFragmentManager();
+ if (active) {
+ fm.beginTransaction()
+ .add(FRAGMENT_HOLDER_ID, searchFragment, PreferenceSearchFragment.NAME)
+ .addToBackStack(PreferenceSearchFragment.NAME)
+ .commit();
+ } else if (searchFragment != null) {
+ fm.beginTransaction().remove(searchFragment).commit();
+ fm.popBackStack(
+ PreferenceSearchFragment.NAME,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE);
+
+ KeyboardUtil.hideKeyboard(this, searchEditText);
+ }
+
+ resetSearchText();
+ }
+
+ private void resetSearchText() {
+ searchEditText.setText("");
+ }
+
+ private boolean isSearchActive() {
+ return searchContainer.getVisibility() == View.VISIBLE;
+ }
+
+ private void onSearchChanged() {
+ if (!isSearchActive()) {
+ return;
+ }
+
+ if (searchFragment != null) {
+ searchFragment.updateSearchResults(this.searchEditText.getText().toString());
+ }
+ }
+
+ @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
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
new file mode 100644
index 000000000..c4db9f93d
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsResourceRegistry.java
@@ -0,0 +1,151 @@
+package org.schabi.newpipe.settings;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+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.
+ *
+ * includes:
+ *