/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.dialer.speeddial; import android.content.Context; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.GridLayoutManager.SpanSizeLookup; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.support.v7.widget.helper.ItemTouchHelper; import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.ViewGroup; import android.view.animation.AnticipateInterpolator; import android.widget.FrameLayout; import com.android.dialer.common.Assert; import com.android.dialer.speeddial.FavoritesViewHolder.FavoriteContactsListener; import com.android.dialer.speeddial.HeaderViewHolder.SpeedDialHeaderListener; import com.android.dialer.speeddial.SpeedDialFragment.HostInterface; import com.android.dialer.speeddial.SuggestionViewHolder.SuggestedContactsListener; import com.android.dialer.speeddial.draghelper.SpeedDialItemTouchHelperCallback.ItemTouchHelperAdapter; import com.android.dialer.speeddial.loader.SpeedDialUiItem; import com.google.common.collect.ImmutableList; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * RecyclerView adapter for {@link SpeedDialFragment}. * *

Displays a list in the following order: * *

    *
  1. Favorite contacts header (with add button) *
  2. Favorite contacts *
  3. Suggested contacts header *
  4. Suggested contacts *
*/ public final class SpeedDialAdapter extends RecyclerView.Adapter implements ItemTouchHelperAdapter { private static final int NON_CONTACT_ITEM_NUMBER_BEFORE_FAVORITES = 2; private static final int NON_CONTACT_ITEM_NUMBER_BEFORE_SUGGESTION = 3; private static final float IN_REMOVE_VIEW_SCALE = 0.5f; private static final float IN_REMOVE_VIEW_ALPHA = 0.5f; @Retention(RetentionPolicy.SOURCE) @IntDef({RowType.STARRED_HEADER, RowType.SUGGESTION_HEADER, RowType.STARRED, RowType.SUGGESTION}) @interface RowType { int REMOVE_VIEW = 0; int STARRED_HEADER = 1; int SUGGESTION_HEADER = 2; int STARRED = 3; int SUGGESTION = 4; } private final Context context; private final FavoriteContactsListener favoritesListener; private final SuggestedContactsListener suggestedListener; private final SpeedDialHeaderListener headerListener; private final HostInterface hostInterface; private final Map positionToRowTypeMap = new ArrayMap<>(); private List speedDialUiItems; // Needed for FavoriteViewHolder private ItemTouchHelper itemTouchHelper; private RemoveViewHolder removeViewHolder; private FavoritesViewHolder draggingFavoritesViewHolder; public SpeedDialAdapter( Context context, FavoriteContactsListener favoritesListener, SuggestedContactsListener suggestedListener, SpeedDialHeaderListener headerListener, HostInterface hostInterface) { this.context = context; this.favoritesListener = favoritesListener; this.suggestedListener = suggestedListener; this.headerListener = headerListener; this.hostInterface = hostInterface; } @Override public int getItemViewType(int position) { return positionToRowTypeMap.get(position); } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(context); switch (viewType) { case RowType.STARRED: return new FavoritesViewHolder( inflater.inflate(R.layout.favorite_item_layout, parent, false), itemTouchHelper, favoritesListener); case RowType.SUGGESTION: return new SuggestionViewHolder( inflater.inflate(R.layout.suggestion_row_layout, parent, false), suggestedListener); case RowType.STARRED_HEADER: case RowType.SUGGESTION_HEADER: return new HeaderViewHolder( inflater.inflate(R.layout.speed_dial_header_layout, parent, false), headerListener); case RowType.REMOVE_VIEW: removeViewHolder = new RemoveViewHolder( inflater.inflate(R.layout.favorite_remove_view_layout, parent, false)); return removeViewHolder; default: throw Assert.createIllegalStateFailException("Invalid viewType: " + viewType); } } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { switch (getItemViewType(position)) { case RowType.STARRED_HEADER: ((HeaderViewHolder) holder).setHeaderText(R.string.favorites_header); ((HeaderViewHolder) holder).showAddButton(true); return; case RowType.SUGGESTION_HEADER: ((HeaderViewHolder) holder).setHeaderText(R.string.suggestions_header); ((HeaderViewHolder) holder).showAddButton(false); return; case RowType.STARRED: ((FavoritesViewHolder) holder).bind(context, speedDialUiItems.get(position - 2)); // Removed item might come back FrameLayout avatarContainer = ((FavoritesViewHolder) holder).getAvatarContainer(); avatarContainer.setScaleX(1); avatarContainer.setScaleY(1); avatarContainer.setAlpha(1); break; case RowType.SUGGESTION: ((SuggestionViewHolder) holder).bind(context, speedDialUiItems.get(position - 3)); break; case RowType.REMOVE_VIEW: break; default: throw Assert.createIllegalStateFailException("Invalid view holder: " + holder); } } @Override public int getItemCount() { return positionToRowTypeMap.size(); } public void setSpeedDialUiItems(List immutableSpeedDialUiItems) { speedDialUiItems = new ArrayList<>(); speedDialUiItems.addAll(immutableSpeedDialUiItems); speedDialUiItems.sort( (o1, o2) -> { if (o1.isStarred() && o2.isStarred()) { return Integer.compare(o1.pinnedPosition().or(-1), o2.pinnedPosition().or(-1)); } return Boolean.compare(o2.isStarred(), o1.isStarred()); }); updatePositionToRowTypeMap(); } private void updatePositionToRowTypeMap() { positionToRowTypeMap.clear(); if (speedDialUiItems.isEmpty()) { return; } positionToRowTypeMap.put(0, RowType.REMOVE_VIEW); // Show the add favorites even if there are no favorite contacts positionToRowTypeMap.put(1, RowType.STARRED_HEADER); int positionOfSuggestionHeader = NON_CONTACT_ITEM_NUMBER_BEFORE_FAVORITES; for (int i = 0; i < speedDialUiItems.size(); i++) { if (speedDialUiItems.get(i).isStarred()) { positionToRowTypeMap.put(i + NON_CONTACT_ITEM_NUMBER_BEFORE_FAVORITES, RowType.STARRED); positionOfSuggestionHeader++; } else { positionToRowTypeMap.put(i + NON_CONTACT_ITEM_NUMBER_BEFORE_SUGGESTION, RowType.SUGGESTION); } } if (!speedDialUiItems.get(speedDialUiItems.size() - 1).isStarred()) { positionToRowTypeMap.put(positionOfSuggestionHeader, RowType.SUGGESTION_HEADER); } } public ImmutableList getSpeedDialUiItems() { if (speedDialUiItems == null || speedDialUiItems.isEmpty()) { return ImmutableList.of(); } return ImmutableList.copyOf(speedDialUiItems); } public SpanSizeLookup getSpanSizeLookup() { return new SpanSizeLookup() { @Override public int getSpanSize(int position) { switch (getItemViewType(position)) { case RowType.SUGGESTION: case RowType.STARRED_HEADER: case RowType.SUGGESTION_HEADER: case RowType.REMOVE_VIEW: return 3; // span the whole screen case RowType.STARRED: return 1; // span 1/3 of the screen default: throw Assert.createIllegalStateFailException( "Invalid row type: " + positionToRowTypeMap.get(position)); } } }; } @Override public void onItemMove(int fromPosition, int toPosition) { if (toPosition == 0) { // drop to removeView return; } // fromPosition/toPosition correspond to adapter position, which is off by 1 from the list // position b/c of the favorites header. So subtract 1 here. speedDialUiItems.add(toPosition - 2, speedDialUiItems.remove(fromPosition - 2)); notifyItemMoved(fromPosition, toPosition); } @Override public boolean canDropOver(ViewHolder target) { return target instanceof FavoritesViewHolder || target instanceof RemoveViewHolder; } @Override public void onSelectedChanged(@Nullable ViewHolder viewHolder, int actionState) { switch (actionState) { case ItemTouchHelper.ACTION_STATE_DRAG: if (viewHolder != null) { draggingFavoritesViewHolder = (FavoritesViewHolder) viewHolder; draggingFavoritesViewHolder.onSelectedChanged(true); hostInterface.dragFavorite(true); removeViewHolder.show(); } break; case ItemTouchHelper.ACTION_STATE_IDLE: // viewHolder is null in this case if (draggingFavoritesViewHolder != null) { draggingFavoritesViewHolder.onSelectedChanged(false); draggingFavoritesViewHolder = null; hostInterface.dragFavorite(false); removeViewHolder.hide(); } break; default: break; } } @Override public void enterRemoveView() { if (draggingFavoritesViewHolder != null) { draggingFavoritesViewHolder .getAvatarContainer() .animate() .scaleX(IN_REMOVE_VIEW_SCALE) .scaleY(IN_REMOVE_VIEW_SCALE) .alpha(IN_REMOVE_VIEW_ALPHA) .start(); } } @Override public void leaveRemoveView() { if (draggingFavoritesViewHolder != null) { draggingFavoritesViewHolder .getAvatarContainer() .animate() .scaleX(1) .scaleY(1) .alpha(1) .start(); } } @Override public void dropOnRemoveView(ViewHolder fromViewHolder) { if (!(fromViewHolder instanceof FavoritesViewHolder)) { return; } int fromPosition = fromViewHolder.getAdapterPosition(); SpeedDialUiItem removedItem = speedDialUiItems.remove(fromPosition - 2); favoritesListener.onRequestRemove(removedItem); ((FavoritesViewHolder) fromViewHolder) .getAvatarContainer() .animate() .scaleX(0) .scaleY(0) .alpha(0) .setInterpolator(new AnticipateInterpolator()) .start(); updatePositionToRowTypeMap(); } public void setItemTouchHelper(ItemTouchHelper itemTouchHelper) { this.itemTouchHelper = itemTouchHelper; } /** Returns true if there are suggested contacts. */ public boolean hasFrequents() { return !speedDialUiItems.isEmpty() && getItemViewType(getItemCount() - 1) == RowType.SUGGESTION; } }