From 3c038aaf7ce4c3bf8c87fce74e1665f626aec356 Mon Sep 17 00:00:00 2001 From: evermind Date: Mon, 31 Oct 2022 23:02:08 +0100 Subject: [PATCH] searchfilters: common base classes for DialogFragment based UI's --- .../filter/BaseCreateSearchFilterUI.java | 123 ++++++++++++++++++ .../list/search/filter/BaseItemWrapper.java | 21 +++ .../BaseSearchFilterDialogFragment.java | 116 +++++++++++++++++ .../BaseSearchFilterUiDialogGenerator.java | 84 ++++++++++++ .../list/search/filter/BaseUiItemWrapper.java | 29 +++++ .../search/filter/UiItemWrapperViews.java | 62 +++++++++ 6 files changed, 435 insertions(+) create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseCreateSearchFilterUI.java create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseItemWrapper.java create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseSearchFilterDialogFragment.java create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseSearchFilterUiDialogGenerator.java create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseUiItemWrapper.java create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/UiItemWrapperViews.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseCreateSearchFilterUI.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseCreateSearchFilterUI.java new file mode 100644 index 000000000..7bf8c4559 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseCreateSearchFilterUI.java @@ -0,0 +1,123 @@ +// Created by evermind-zz 2022, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.fragments.list.search.filter; + +import android.content.Context; +import android.view.View; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.search.filter.FilterGroup; +import org.schabi.newpipe.extractor.search.filter.FilterItem; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; + +import static org.schabi.newpipe.fragments.list.search.filter.SearchFilterLogic.ICreateUiForFiltersWorker; + +/** + * Common base for the {@link SearchFilterDialogGenerator} and + * {@link SearchFilterOptionMenuAlikeDialogGenerator}'s + * {@link ICreateUiForFiltersWorker} implementation. + */ +public abstract class BaseCreateSearchFilterUI + implements ICreateUiForFiltersWorker { + + @NonNull + protected final BaseSearchFilterUiDialogGenerator dialogGenBase; + @NonNull + protected final Context context; + protected final List titleViewElements = new ArrayList<>(); + protected final SearchFilterLogic logic; + protected int titleResId; + + protected BaseCreateSearchFilterUI( + @NonNull final BaseSearchFilterUiDialogGenerator dialogGenBase, + @NonNull final SearchFilterLogic logic, + @NonNull final Context context, + final int titleResId) { + this.dialogGenBase = dialogGenBase; + this.logic = logic; + this.context = context; + this.titleResId = titleResId; + } + + @Override + public void createFilterItem(@NonNull final FilterItem filterItem, + @NonNull final FilterGroup filterGroup) { + // no implementation here all creation stuff is done in createFilterGroupBeforeItems + } + + @Override + public void createFilterGroupAfterItems(@NonNull final FilterGroup filterGroup) { + // no implementation here all creation stuff is done in createFilterGroupBeforeItems + } + + @Override + public void finish() { + // no implementation here all creation stuff is done in createFilterGroupBeforeItems + } + + /** + * This method is used to control the visibility of the title 'sort filter' if the + * chosen content filter has no sort filters. + * + * @param areFiltersVisible true if filter visible + */ + @Override + public void filtersVisible(final boolean areFiltersVisible) { + final int visibility = areFiltersVisible ? View.VISIBLE : View.GONE; + for (final View view : titleViewElements) { + if (view != null) { + view.setVisibility(visibility); + } + } + } + + public static class CreateContentFilterUI extends CreateSortFilterUI { + + public CreateContentFilterUI( + @NonNull final BaseSearchFilterUiDialogGenerator dialogGenBase, + @NonNull final Context context, + @NonNull final SearchFilterLogic logic) { + super(dialogGenBase, context, logic); + this.titleResId = R.string.filter_search_content_filters; + } + + @Override + public void createFilterGroupBeforeItems( + @NonNull final FilterGroup filterGroup) { + dialogGenBase.createFilterGroup(filterGroup, + logic::addContentFilterUiWrapperToItemMap, + logic::selectContentFilter); + } + + @Override + public void filtersVisible(final boolean areFiltersVisible) { + // no implementation here. As content filters have to be always visible + } + } + + public static class CreateSortFilterUI extends BaseCreateSearchFilterUI { + + public CreateSortFilterUI( + @NonNull final BaseSearchFilterUiDialogGenerator dialogGenBase, + @NonNull final Context context, + @NonNull final SearchFilterLogic logic) { + super(dialogGenBase, logic, context, R.string.filter_search_sort_filters); + } + + @Override + public void prepare() { + dialogGenBase.createTitle(context.getString(titleResId), titleViewElements); + } + + @Override + public void createFilterGroupBeforeItems(@NonNull final FilterGroup filterGroup) { + dialogGenBase.createFilterGroup(filterGroup, + logic::addSortFilterUiWrapperToItemMap, + logic::selectSortFilter); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseItemWrapper.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseItemWrapper.java new file mode 100644 index 000000000..cba5b3c7f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseItemWrapper.java @@ -0,0 +1,21 @@ +// Created by evermind-zz 2022, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.fragments.list.search.filter; + +import org.schabi.newpipe.extractor.search.filter.FilterItem; + +import androidx.annotation.NonNull; + +public abstract class BaseItemWrapper implements SearchFilterLogic.IUiItemWrapper { + @NonNull + protected final FilterItem item; + + protected BaseItemWrapper(@NonNull final FilterItem item) { + this.item = item; + } + + @Override + public int getItemId() { + return item.getIdentifier(); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseSearchFilterDialogFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseSearchFilterDialogFragment.java new file mode 100644 index 000000000..087abd7c8 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseSearchFilterDialogFragment.java @@ -0,0 +1,116 @@ +// Created by evermind-zz 2022, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.fragments.list.search.filter; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.fragments.list.search.SearchViewModel; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.ViewModelProvider; + +/** + * Base dialog class for {@link DialogFragment} based search filter dialogs. + */ +public abstract class BaseSearchFilterDialogFragment extends DialogFragment { + + protected BaseSearchFilterUiGenerator dialogGenerator; + protected SearchViewModel searchViewModel; + + private void createSearchFilterUi() { + dialogGenerator = createSearchFilterDialogGenerator(); + dialogGenerator.createSearchUI(); + } + + @Override + public void show(@NonNull final FragmentManager manager, @Nullable final String tag) { + // Avoid multiple instances of the dialog that could be triggered by multiple taps + if (manager.findFragmentByTag(tag) == null) { + super.show(manager, tag); + } + } + + protected abstract BaseSearchFilterUiGenerator createSearchFilterDialogGenerator(); + + /** + * As we have different bindings we need to get this sorted in a method. + * + * @return the {@link Toolbar} null if there is no toolbar available. + */ + @Nullable + protected abstract Toolbar getToolbar(); + + protected abstract View getRootView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container); + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Make sure that the first parameter is pointing to instance of SearchFragment otherwise + // another SearchViewModel object will be created instead of the existing one used. + // -> the SearchViewModel is first instantiated in SearchFragment. Here we just use it. + searchViewModel = + new ViewModelProvider(requireParentFragment()).get(SearchViewModel.class); + } + + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + final Bundle savedInstanceState) { + final View rootView = getRootView(inflater, container); + createSearchFilterUi(); + return rootView; + } + + @Override + public void onViewCreated(@NonNull final View view, final Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + final Toolbar toolbar = getToolbar(); + if (toolbar != null) { + initToolbar(toolbar); + } + } + + /** + * Initialize the toolbar. + *

+ * This method is only called if {@link #getToolbar()} is implemented to return a toolbar. + * + * @param toolbar the actual toolbar for this dialog fragment + */ + protected void initToolbar(@NonNull final Toolbar toolbar) { + toolbar.setTitle(R.string.filter); + toolbar.setNavigationIcon(R.drawable.ic_arrow_back); + toolbar.inflateMenu(R.menu.menu_search_filter_dialog_fragment); + toolbar.setNavigationOnClickListener(v -> dismiss()); + toolbar.setNavigationContentDescription(R.string.cancel); + + final View okButton = toolbar.findViewById(R.id.search); + okButton.setEnabled(true); + + final View resetButton = toolbar.findViewById(R.id.reset); + resetButton.setEnabled(true); + + toolbar.setOnMenuItemClickListener(item -> { + if (item.getItemId() == R.id.search) { + searchViewModel.getSearchFilterLogic().prepareForSearch(); + dismiss(); + return true; + } else if (item.getItemId() == R.id.reset) { + searchViewModel.getSearchFilterLogic().reset(); + return true; + } + return false; + }); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseSearchFilterUiDialogGenerator.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseSearchFilterUiDialogGenerator.java new file mode 100644 index 000000000..6beaca67a --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseSearchFilterUiDialogGenerator.java @@ -0,0 +1,84 @@ +// Created by evermind-zz 2022, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.fragments.list.search.filter; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.schabi.newpipe.extractor.search.filter.FilterGroup; + +import java.util.List; + +import androidx.annotation.NonNull; + +import static android.util.TypedValue.COMPLEX_UNIT_DIP; +import static org.schabi.newpipe.fragments.list.search.filter.SearchFilterLogic.ICreateUiForFiltersWorker; + +public abstract class BaseSearchFilterUiDialogGenerator extends BaseSearchFilterUiGenerator { + private static final float FONT_SIZE_TITLE_ITEMS_IN_DIP = 20f; + + protected BaseSearchFilterUiDialogGenerator( + @NonNull final SearchFilterLogic logic, + @NonNull final Context context) { + super(logic, context); + } + + protected abstract void createTitle(@NonNull String name, + @NonNull List titleViewElements); + + protected abstract void createFilterGroup(@NonNull FilterGroup filterGroup, + @NonNull UiWrapperMapDelegate wrapperDelegate, + @NonNull UiSelectorDelegate selectorDelegate); + + @Override + protected ICreateUiForFiltersWorker createContentFilterWorker() { + return new BaseCreateSearchFilterUI.CreateContentFilterUI(this, context, logic); + } + + @Override + protected ICreateUiForFiltersWorker createSortFilterWorker() { + return new BaseCreateSearchFilterUI.CreateSortFilterUI(this, context, logic); + } + + /** + * Create a View that acts as a separator between two other {@link View}-Elements. + * + * @param layoutParams this layout will be modified to have the height of 1 -> to have a + * the actual separator line. + * @return the created {@link SeparatorLineView} + */ + @NonNull + protected SeparatorLineView createSeparatorLine( + @NonNull final ViewGroup.LayoutParams layoutParams) { + final SeparatorLineView separatorLine = new SeparatorLineView(context); + separatorLine.setBackgroundColor(getSeparatorLineColorFromTheme()); + layoutParams.height = 1; // always set the separator to the height of 1 + separatorLine.setLayoutParams(layoutParams); + return separatorLine; + } + + @NonNull + protected TextView createTitleText(@NonNull final String name, + @NonNull final ViewGroup.LayoutParams layoutParams) { + final TextView title = new TextView(context); + title.setText(name); + title.setTextSize(COMPLEX_UNIT_DIP, FONT_SIZE_TITLE_ITEMS_IN_DIP); + title.setLayoutParams(layoutParams); + return title; + } + + /** + * A special view to separate two other {@link View}s. + *

+ * class only needed to distinct this special view from other View based views. + * (eg. instanceof) + */ + protected static final class SeparatorLineView extends View { + + private SeparatorLineView(@NonNull final Context context) { + super(context); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseUiItemWrapper.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseUiItemWrapper.java new file mode 100644 index 000000000..7a2f876c5 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/BaseUiItemWrapper.java @@ -0,0 +1,29 @@ +// Created by evermind-zz 2022, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.fragments.list.search.filter; + +import android.view.View; + +import org.schabi.newpipe.extractor.search.filter.FilterItem; + +import androidx.annotation.NonNull; + +public abstract class BaseUiItemWrapper extends BaseItemWrapper { + @NonNull + protected final View view; + + protected BaseUiItemWrapper(@NonNull final FilterItem item, + @NonNull final View view) { + super(item); + this.view = view; + } + + @Override + public void setVisible(final boolean visible) { + if (visible) { + view.setVisibility(View.VISIBLE); + } else { + view.setVisibility(View.GONE); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/UiItemWrapperViews.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/UiItemWrapperViews.java new file mode 100644 index 000000000..f6b0ed1d3 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/filter/UiItemWrapperViews.java @@ -0,0 +1,62 @@ +// Created by evermind-zz 2022, licensed GNU GPL version 3 or later + +package org.schabi.newpipe.fragments.list.search.filter; + +import android.view.View; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; + +/** + * Wrapper for views that are either just labels or eg. a RadioGroup container + * etc. that represent a {@link org.schabi.newpipe.extractor.search.filter.FilterGroup}. + */ +final class UiItemWrapperViews implements SearchFilterLogic.IUiItemWrapper { + + private final int itemId; + private final List views = new ArrayList<>(); + + UiItemWrapperViews(final int itemId) { + this.itemId = itemId; + } + + public void add(@NonNull final View view) { + this.views.add(view); + } + + @Override + public void setVisible(final boolean visible) { + for (final View view : views) { + if (visible) { + view.setVisibility(View.VISIBLE); + } else { + view.setVisibility(View.GONE); + } + } + } + + @Override + public int getItemId() { + return this.itemId; + } + + @Override + public boolean isChecked() { + boolean isChecked = false; + for (final View view : views) { + if (view.isSelected()) { + isChecked = true; + break; + } + } + return isChecked; + } + + @Override + public void setChecked(final boolean checked) { + // not relevant as here views are wrapped that are either just labels or eg. a + // RadioGroup container etc. that represent a FilterGroup. + } +}