Applied code changes for preference search framework

This commit is contained in:
litetex 2021-12-24 21:35:15 +01:00
parent 12a78a826d
commit 07fb319e88
11 changed files with 408 additions and 17 deletions

View File

@ -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)

View File

@ -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) {

View File

@ -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) -> {

View File

@ -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);

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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))

View File

@ -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
}

View File

@ -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.
* <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(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;
}
@Nullable
public SettingRegistryEntry getEntryByFragmentClass(
final Class<? extends Fragment> fragmentClass
) {
Objects.requireNonNull(fragmentClass);
return registeredEntries.stream()
.filter(e -> Objects.equals(e.getFragmentClass(), fragmentClass))
.findFirst()
.orElse(null);
}
@Nullable
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();
}
@Nullable
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());
}
}
}

View File

@ -38,7 +38,7 @@ public class UpdateSettingsFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.update_settings);
addPreferencesFromResourceRegistry();
findPreference(getString(R.string.update_app_key))
.setOnPreferenceChangeListener(updatePreferenceChange);

View File

@ -23,7 +23,7 @@ public class VideoAudioSettingsFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.video_audio_settings);
addPreferencesFromResourceRegistry();
updateSeekOptions();