blob: 7d5336a0d734888c1caec7c70a24d7de2b0ae0cd [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
* Copyright (C) 2023 The LineageOS 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.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.animation.AnticipateInterpolator;
import android.widget.FrameLayout;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.dialer.R;
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}.
*
* <p>Displays a list in the following order:
*
* <ol>
* <li>Favorite contacts header (with add button)
* <li>Favorite contacts
* <li>Suggested contacts header
* <li>Suggested contacts
* </ol>
*/
public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
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.REMOVE_VIEW, 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<Integer, Integer> positionToRowTypeMap = new ArrayMap<>();
private List<SpeedDialUiItem> 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<SpeedDialUiItem> immutableSpeedDialUiItems) {
speedDialUiItems = new ArrayList<>();
speedDialUiItems.addAll(immutableSpeedDialUiItems);
speedDialUiItems.sort(
(o1, o2) -> {
if (o1.isStarred() && o2.isStarred()) {
return Integer.compare(o1.pinnedPosition().orElse(-1), o2.pinnedPosition().orElse(-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<SpeedDialUiItem> getSpeedDialUiItems() {
if (speedDialUiItems == null || speedDialUiItems.isEmpty()) {
return ImmutableList.of();
}
return ImmutableList.copyOf(speedDialUiItems);
}
public GridLayoutManager.SpanSizeLookup getSpanSizeLookup() {
return new GridLayoutManager.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;
}
}