searchfilters: common base classes for DialogFragment based UI's

This commit is contained in:
evermind 2022-10-31 23:02:08 +01:00
parent 651a333393
commit 3c038aaf7c
6 changed files with 435 additions and 0 deletions

View File

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

View File

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

View File

@ -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.
* <p>
* 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;
});
}
}

View File

@ -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<View> 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.
* <p>
* 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);
}
}
}

View File

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

View File

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