package com.kickstarter.libs.utils; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Pair; import com.kickstarter.models.Category; import com.kickstarter.models.User; import com.kickstarter.services.DiscoveryParams; import com.kickstarter.ui.adapters.data.NavigationDrawerData; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; import rx.Observable; import static com.kickstarter.libs.utils.BooleanUtils.isTrue; public final class DiscoveryDrawerUtils { private DiscoveryDrawerUtils() {} /** * Converts all the disparate data representing the state of the menu data into a `NavigationDrawerData` object * that can be used to populate a view. * * @param categories The full list of categories that can be displayed. * @param selected The params that correspond to what is currently selected in the menu. * @param expandedCategory The category that correspond to what is currently expanded in the menu. * @param user The currently logged in user. */ public static @NonNull NavigationDrawerData deriveNavigationDrawerData(final @NonNull List<Category> categories, final @NonNull DiscoveryParams selected, final @Nullable Category expandedCategory, final @Nullable User user) { final NavigationDrawerData.Builder builder = NavigationDrawerData.builder(); final List<NavigationDrawerData.Section> categorySections = Observable.from(categories) .filter(c -> isVisible(c, expandedCategory)) .flatMap(c -> doubleRootIfExpanded(c, expandedCategory)) .map(c -> DiscoveryParams.builder().category(c).build()) .toList() .map(DiscoveryDrawerUtils::paramsGroupedByRootCategory) .map(sections -> sectionsFromAllParams(sections, expandedCategory)) .toBlocking().single(); final List<NavigationDrawerData.Section> sections = Observable .from(categorySections) .startWith(topSections(user)) .toList().toBlocking().single(); return builder .sections(sections) .user(user) .selectedParams(selected) .expandedCategory(expandedCategory) .build(); } /** * Given a doubly nested list of all possible category params and an (optional) expanded category this will * create a list of sections that can be used in the drawer. */ private static @NonNull List<NavigationDrawerData.Section> sectionsFromAllParams(final @NonNull List<List<DiscoveryParams>> sections, final @Nullable Category expandedCategory) { return Observable.from(sections) .map(DiscoveryDrawerUtils::rowsFromParams) .map(rows -> Pair.create(rows, rowsAreExpanded(rows, expandedCategory))) .map(rowsAndIsExpanded -> NavigationDrawerData.Section.builder() .rows(rowsAndIsExpanded.first) .expanded(rowsAndIsExpanded.second) .build() ) .toList().toBlocking().single(); } /** * Converts a list of params into a list of rows that the drawer can use to display rows. */ private static @NonNull List<NavigationDrawerData.Section.Row> rowsFromParams(final @NonNull List<DiscoveryParams> params) { return Observable.from(params) .map(p -> NavigationDrawerData.Section.Row.builder().params(p).build()) .toList().toBlocking().single(); } /** * From a list of rows and the currently expanded category figures out if the rows are expanded. */ private static boolean rowsAreExpanded(final List<NavigationDrawerData.Section.Row> rows, final @Nullable Category expandedCategory) { final Category sectionCategory = rows.get(0).params().category(); return sectionCategory != null && expandedCategory != null && sectionCategory.rootId() == expandedCategory.rootId(); } /** * Determines if a category is visible given what is the currently expanded category. * @param category The category to determine its visibility. * @param expandedCategory The category that is currently expandable, possible `null`. */ private static boolean isVisible(final @NonNull Category category, final @Nullable Category expandedCategory) { if (expandedCategory == null) { return category.isRoot(); } if (category.isRoot()) { return true; } return category.root().id() == expandedCategory.id(); } /** * Since there are two rows that correspond to a root category in an expanded section (e.g. "Art" & "All of Art"), * this method will double up that root category in such a situation. * @param category The category that might potentially be doubled up. * @param expandedCategory The currently expanded category. */ private static @NonNull Observable<Category> doubleRootIfExpanded(final @NonNull Category category, final @Nullable Category expandedCategory) { if (expandedCategory == null) { return Observable.just(category); } if (category.isRoot() && category.id() == expandedCategory.id()) { return Observable.just(category, category); } return Observable.just(category); } /** * Returns a list of top-level section filters that can be used based on the current user, which could be `null`. * Each filter is its own section containing one single row. * * @param user The currently logged in user, can be `null`. */ private static @NonNull List<NavigationDrawerData.Section> topSections(final @Nullable User user) { final List<DiscoveryParams> filters = ListUtils.empty(); filters.add(DiscoveryParams.builder().build()); filters.add(DiscoveryParams.builder().staffPicks(true).build()); if (user != null) { filters.add(DiscoveryParams.builder().starred(1).build()); filters.add(DiscoveryParams.builder().recommended(true).backed(-1).build()); if (isTrue(user.social())) { filters.add(DiscoveryParams.builder().social(1).build()); } } return Observable.from(filters) .map(p -> NavigationDrawerData.Section.Row.builder().params(p).build()) .map(Collections::singletonList) .map(rows -> NavigationDrawerData.Section.builder().rows(rows).build()) .toList().toBlocking().single(); } /** * Converts the full list of category discovery params into a grouped list of params. A group corresponds to a root * category, and the list contains all subcategories. */ private static @NonNull List<List<DiscoveryParams>> paramsGroupedByRootCategory(final @NonNull List<DiscoveryParams> ps) { final Map<String, List<DiscoveryParams>> grouped = new TreeMap<>(); for (final DiscoveryParams p : ps) { if (!grouped.containsKey(p.category().root().name())) { grouped.put(p.category().root().name(), new ArrayList<>()); } grouped.get(p.category().root().name()).add(p); } return new ArrayList<>(grouped.values()); } }