searchfilters: common base classes for DialogFragment based UI's

This commit is contained in:
evermind 2022-10-31 23:02:08 +01:00
parent 5b95094a77
commit 55240193c0
5 changed files with 481 additions and 0 deletions

View file

@ -0,0 +1,108 @@
// 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;
/**
* Common base for the {@link SearchFilterDialogGenerator} and
* {@link SearchFilterOptionMenuAlikeDialogGenerator}'s
* {@link SearchFilterLogic.ICreateUiForFiltersWorker} implementation.
*/
public abstract class BaseCreateSearchFilterUI
implements SearchFilterLogic.ICreateUiForFiltersWorker {
protected final BaseSearchFilterUiDialogGenerator dialogGenBase;
protected final Context context;
protected final List<View> titleViewElements = new ArrayList<>();
protected int titleResId;
protected BaseCreateSearchFilterUI(final BaseSearchFilterUiDialogGenerator dialogGenBase,
final Context context,
final int titleResId) {
this.dialogGenBase = dialogGenBase;
this.context = context;
this.titleResId = titleResId;
}
@Override
public void createFilterItem(final FilterItem filterItem, final FilterGroup filterGroup) {
// no implementation here all creation stuff is done in createFilterGroupBeforeItems
}
@Override
public void createFilterGroupAfterItems(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(final BaseSearchFilterUiDialogGenerator dialogGenBase,
final Context context) {
super(dialogGenBase, context);
this.titleResId = R.string.filter_search_content_filters;
}
@Override
public void createFilterGroupBeforeItems(
final FilterGroup filterGroup) {
dialogGenBase.createFilterGroup(filterGroup,
dialogGenBase::addContentFilterUiWrapperToItemMap,
dialogGenBase::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(final BaseSearchFilterUiDialogGenerator dialogGenBase,
final Context context) {
super(dialogGenBase, context, R.string.filter_search_sort_filters);
}
@Override
public void prepare() {
dialogGenBase.createTitle(context.getString(titleResId), titleViewElements);
}
@Override
public void createFilterGroupBeforeItems(final FilterGroup filterGroup) {
dialogGenBase.createFilterGroup(filterGroup,
dialogGenBase::addSortFilterUiWrapperToItemMap,
dialogGenBase::selectSortFilter);
}
}
}

View file

@ -0,0 +1,188 @@
// 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.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.search.filter.FilterItem;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.DialogFragment;
import icepick.Icepick;
import icepick.State;
/**
* Base dialog class for {@link DialogFragment} based search filter dialogs.
*/
public abstract class BaseSearchFilterDialogFragment extends DialogFragment {
private static final String CONTENT_FILTERS = "CONTENT_FILTERS";
private static final String SORT_FILTERS = "SORT_FILTERS";
private static final String SERVICE_ID = "SERVICE_ID";
@State
public ArrayList<Integer> userSelectedContentFilterList;
protected List<FilterItem> selectedContentFilters;
protected List<FilterItem> selectedSortFilters;
protected BaseSearchFilterUiGenerator dialogGenerator;
@State
ArrayList<Integer> userSelectedSortFilterList = null;
protected static DialogFragment initDialogArguments(
final DialogFragment dialogFragment,
final int serviceId,
final ArrayList<Integer> userSelectedContentFilter,
final ArrayList<Integer> userSelectedSortFilter) {
final Bundle bundle = new Bundle(1);
bundle.putInt(SERVICE_ID, serviceId);
bundle.putIntegerArrayList(CONTENT_FILTERS, userSelectedContentFilter);
bundle.putIntegerArrayList(SORT_FILTERS, userSelectedSortFilter);
dialogFragment.setArguments(bundle);
return dialogFragment;
}
private void initializeFilterData() {
assert getArguments() != null;
final int serviceId = getArguments().getInt(SERVICE_ID);
final ArrayList<Integer> contentFilters =
getArguments().getIntegerArrayList(CONTENT_FILTERS);
final ArrayList<Integer> sortFilters =
getArguments().getIntegerArrayList(SORT_FILTERS);
final StreamingService service;
try {
service = NewPipe.getService(serviceId);
} catch (final ExtractionException e) {
throw new RuntimeException(e);
}
dialogGenerator = createSearchFilterDialogGenerator(service,
(userSelectedContentFilter, userSelectedSortFilter) -> {
selectedContentFilters = userSelectedContentFilter;
selectedSortFilters = userSelectedSortFilter;
sendDataToParentFragment();
});
userSelectedContentFilterList = contentFilters;
userSelectedSortFilterList = sortFilters;
dialogGenerator.restorePreviouslySelectedFilters(
userSelectedContentFilterList,
userSelectedSortFilterList);
dialogGenerator.createSearchUI();
}
protected abstract BaseSearchFilterUiGenerator createSearchFilterDialogGenerator(
StreamingService service,
SearchFilterLogic.Callback callback);
/**
* As we have different bindings we need to get this sorted in a method.
*
* @return the {@link Toolbar}
*/
protected abstract Toolbar getToolbar();
protected abstract View getRootView(LayoutInflater inflater,
ViewGroup container);
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
final View rootView = getRootView(inflater, container);
initializeFilterData();
return rootView;
}
@Override
public void onResume() {
super.onResume();
dialogGenerator.onResume();
}
@Override
public void onStop() {
dialogGenerator.onPause();
super.onStop();
}
@Override
public void onViewCreated(@NonNull final View view, final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
initToolbar(getToolbar());
}
protected void initToolbar(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) {
dialogGenerator.prepareForSearch();
return true;
} else if (item.getItemId() == R.id.reset) {
dialogGenerator.reset();
return true;
}
return false;
});
}
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
// get data to save its state via Icepick
userSelectedContentFilterList = dialogGenerator.getSelectedContentFilters();
userSelectedSortFilterList = dialogGenerator.getSelectedSortFilters();
Icepick.saveInstanceState(this, outState);
}
private void sendDataToParentFragment() {
final Listener listener = (Listener) getTargetFragment();
if (listener != null) {
listener.onFinishSearchFilterDialog(
userSelectedContentFilterList, userSelectedSortFilterList,
selectedContentFilters, selectedSortFilters);
}
dismiss();
}
/**
* Listener to be implemented by the parent Fragment so it can receive data.
*/
public interface Listener {
void onFinishSearchFilterDialog(ArrayList<Integer> userSelectedContentFilterList,
ArrayList<Integer> userSelectedSortFilterList,
List<FilterItem> selectedContentFilters,
List<FilterItem> selectedSortFilters);
}
}

View file

@ -0,0 +1,78 @@
// 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.linkhandler.SearchQueryHandlerFactory;
import org.schabi.newpipe.extractor.search.filter.FilterGroup;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static android.util.TypedValue.COMPLEX_UNIT_DIP;
public abstract class BaseSearchFilterUiDialogGenerator extends BaseSearchFilterUiGenerator {
private static final float FONT_SIZE_TITLE_ITEMS_IN_DIP = 20f;
protected final Map<View, View.OnClickListener> viewListeners = new HashMap<>();
protected BaseSearchFilterUiDialogGenerator(
final SearchQueryHandlerFactory linkHandlerFactory,
final Callback callback,
final Context context) {
super(linkHandlerFactory, callback, context);
}
protected abstract void createTitle(String name, List<View> titleViewElements);
protected abstract void createFilterGroup(FilterGroup filterGroup,
UiWrapperMapDelegate wrapperDelegate,
UiSelectorDelegate selectorDelegate);
@Override
protected ICreateUiForFiltersWorker createContentFilterWorker() {
return new BaseCreateSearchFilterUI.CreateContentFilterUI(this, context);
}
@Override
protected ICreateUiForFiltersWorker createSortFilterWorker() {
return new BaseCreateSearchFilterUI.CreateSortFilterUI(this, context);
}
@Override
public void onResume() {
for (final Map.Entry<View, View.OnClickListener> view : viewListeners.entrySet()) {
view.getKey().setOnClickListener(view.getValue());
}
}
@Override
public void onPause() {
for (final Map.Entry<View, View.OnClickListener> view : viewListeners.entrySet()) {
view.getKey().setOnClickListener(null);
}
}
protected View createSeparatorLine(final ViewGroup.LayoutParams layoutParams) {
final View separatorLine = new View(context);
separatorLine.setBackgroundColor(getSeparatorLineColorFromTheme());
layoutParams.height = 1; // always set the separator to 1
separatorLine.setLayoutParams(layoutParams);
return separatorLine;
}
protected TextView createTitleText(final String name,
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;
}
}

View file

@ -0,0 +1,40 @@
// 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;
public abstract class BaseUiItemWrapper implements SearchFilterLogic.IUiItemWrapper {
protected final FilterItem item;
protected final int groupId;
protected View view;
protected BaseUiItemWrapper(final FilterItem item,
final int groupId,
final View view) {
this.item = item;
this.groupId = groupId;
this.view = view;
}
@Override
public void setVisible(final boolean visible) {
if (visible) {
view.setVisibility(View.VISIBLE);
} else {
view.setVisibility(View.GONE);
}
}
@Override
public int getItemId() {
return item.getIdentifier();
}
@Override
public int getGroupId() {
return this.groupId;
}
}

View file

@ -0,0 +1,67 @@
// 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;
/**
* 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 int groupId;
private final List<View> views = new ArrayList<>();
UiItemWrapperViews(final int itemId, final int groupId) {
this.itemId = itemId;
this.groupId = groupId;
}
public void add(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 int getGroupId() {
return this.groupId;
}
@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.
}
}