Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 406de13a authored by yueg's avatar yueg Committed by Copybara-Service
Browse files

Drag favorite to remove

Test: RemoveViewHolderTest, SpeedDialAdapterTest
PiperOrigin-RevId: 201266033
Change-Id: Ie7ed9bac8ad9c7bbc35c351409b629e3fbad3de8
parent acfab4ed
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -19,7 +19,9 @@
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    android:layout_height="match_parent"
    android:clipChildren="false"
    android:clipToPadding="false">

  <!-- MainToolbar -->
  <include
@@ -33,13 +35,17 @@
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_below="@+id/toolbar"
      android:layout_above="@+id/bottom_nav_bar">
      android:layout_above="@+id/bottom_nav_bar"
      android:clipChildren="false"
      android:clipToPadding="false">

    <!-- Holds SpeedDial, Call Log, Contacts, Voicemail and Search fragments -->
    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"/>

    <FrameLayout
        android:id="@+id/search_fragment_container"
+15 −0
Original line number Diff line number Diff line
@@ -46,6 +46,8 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder
  private final TextView phoneType;
  private final FrameLayout videoCallIcon;

  private final FrameLayout avatarContainer;

  private SpeedDialUiItem speedDialUiItem;

  public FavoritesViewHolder(View view, ItemTouchHelper helper, FavoriteContactsListener listener) {
@@ -54,6 +56,7 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder
    nameView = view.findViewById(R.id.name);
    phoneType = view.findViewById(R.id.phone_type);
    videoCallIcon = view.findViewById(R.id.video_call_container);
    avatarContainer = view.findViewById(R.id.avatar_container);
    view.setOnClickListener(this);
    view.setOnLongClickListener(this);
    view.setOnTouchListener(
@@ -117,6 +120,15 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder
    listener.onTouchFinished(closeContextMenu);
  }

  FrameLayout getAvatarContainer() {
    return avatarContainer;
  }

  void onSelectedChanged(boolean selected) {
    nameView.setVisibility(selected ? View.GONE : View.VISIBLE);
    phoneType.setVisibility(selected ? View.GONE : View.VISIBLE);
  }

  /** Listener/callback for {@link FavoritesViewHolder} actions. */
  public interface FavoriteContactsListener {

@@ -131,5 +143,8 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder

    /** Called when the user is no longer touching the favorite contact. */
    void onTouchFinished(boolean closeContextMenu);

    /** Called when the user drag the favorite to remove. */
    void onRequestRemove(SpeedDialUiItem speedDialUiItem);
  }
}
+49 −0
Original line number Diff line number Diff line
/*
 * 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.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.View.OnClickListener;

/** ViewHolder for headers in {@link SpeedDialFragment}. */
public class RemoveViewHolder extends RecyclerView.ViewHolder implements OnClickListener {

  private final View removeViewContent;

  RemoveViewHolder(View view) {
    super(view);
    removeViewContent = view;
  }

  void show() {
    removeViewContent.setVisibility(View.VISIBLE);
    removeViewContent.setAlpha(0);
    removeViewContent.animate().alpha(1).start();
  }

  void hide() {
    removeViewContent.setVisibility(View.INVISIBLE);
    removeViewContent.setAlpha(1);
    removeViewContent.animate().alpha(0).start();
  }

  @Override
  public void onClick(View v) {
    // Not clickable
  }
}
+116 −12
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.Context;
import android.os.Build.VERSION_CODES;
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;
@@ -28,6 +29,8 @@ 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;
@@ -58,13 +61,20 @@ import java.util.Map;
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.STARRED_HEADER, RowType.SUGGESTION_HEADER, RowType.STARRED, RowType.SUGGESTION})
  @interface RowType {
    int STARRED_HEADER = 0;
    int SUGGESTION_HEADER = 1;
    int STARRED = 2;
    int SUGGESTION = 3;
    int REMOVE_VIEW = 0;
    int STARRED_HEADER = 1;
    int SUGGESTION_HEADER = 2;
    int STARRED = 3;
    int SUGGESTION = 4;
  }

  private final Context context;
@@ -78,6 +88,9 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi
  // Needed for FavoriteViewHolder
  private ItemTouchHelper itemTouchHelper;

  private RemoveViewHolder removeViewHolder;
  private FavoritesViewHolder draggingFavoritesViewHolder;

  public SpeedDialAdapter(
      Context context,
      FavoriteContactsListener favoritesListener,
@@ -111,6 +124,11 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi
      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);
    }
@@ -128,10 +146,17 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi
        ((HeaderViewHolder) holder).showAddButton(false);
        return;
      case RowType.STARRED:
        ((FavoritesViewHolder) holder).bind(context, speedDialUiItems.get(position - 1));
        ((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 - 2));
        ((SuggestionViewHolder) holder).bind(context, speedDialUiItems.get(position - 3));
        break;
      case RowType.REMOVE_VIEW:
        break;
      default:
        throw Assert.createIllegalStateFailException("Invalid view holder: " + holder);
@@ -153,20 +178,25 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi
          }
          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(0, RowType.STARRED_HEADER);
    int positionOfSuggestionHeader = 1;
    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 + 1, RowType.STARRED); // +1 for the header
        positionToRowTypeMap.put(i + NON_CONTACT_ITEM_NUMBER_BEFORE_FAVORITES, RowType.STARRED);
        positionOfSuggestionHeader++;
      } else {
        positionToRowTypeMap.put(i + 2, RowType.SUGGESTION); // +2 for both headers
        positionToRowTypeMap.put(i + NON_CONTACT_ITEM_NUMBER_BEFORE_SUGGESTION, RowType.SUGGESTION);
      }
    }
    if (!speedDialUiItems.get(speedDialUiItems.size() - 1).isStarred()) {
@@ -189,6 +219,7 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi
          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
@@ -202,15 +233,88 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi

  @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 - 1, speedDialUiItems.remove(fromPosition - 1));
    speedDialUiItems.add(toPosition - 2, speedDialUiItems.remove(fromPosition - 2));
    notifyItemMoved(fromPosition, toPosition);
  }

  @Override
  public boolean canDropOver(ViewHolder target) {
    return target instanceof FavoritesViewHolder;
    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);
          removeViewHolder.show();
        }
        break;
      case ItemTouchHelper.ACTION_STATE_IDLE:
        // viewHolder is null in this case
        if (draggingFavoritesViewHolder != null) {
          draggingFavoritesViewHolder.onSelectedChanged(false);
          draggingFavoritesViewHolder = null;
          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) {
+71 −67
Original line number Diff line number Diff line
@@ -151,9 +151,9 @@ public class SpeedDialFragment extends Fragment {
        new SpeedDialFavoritesListener(
            getActivity(),
            getChildFragmentManager(),
            new SpeedDialContextMenuItemListener(
                getActivity(), new UpdateSpeedDialAdapterListener(), speedDialLoaderListener),
            layoutManager);
            layoutManager,
            new UpdateSpeedDialAdapterListener(),
            speedDialLoaderListener);
    adapter =
        new SpeedDialAdapter(getContext(), favoritesListener, suggestedListener, headerListener);
    layoutManager.setSpanSizeLookup(adapter.getSpanSizeLookup());
@@ -339,20 +339,26 @@ public class SpeedDialFragment extends Fragment {

    private final FragmentActivity activity;
    private final FragmentManager childFragmentManager;
    private final ContextMenuItemListener contextMenuListener;
    private final SpeedDialLayoutManager layoutManager;
    private final UpdateSpeedDialAdapterListener updateAdapterListener;
    private final SupportUiListener<ImmutableList<SpeedDialUiItem>> speedDialLoaderListener;

    private final SpeedDialContextMenuItemListener speedDialContextMenuItemListener =
        new SpeedDialContextMenuItemListener();

    private ContextMenu contextMenu;

    SpeedDialFavoritesListener(
        FragmentActivity activity,
        FragmentManager childFragmentManager,
        ContextMenuItemListener contextMenuListener,
        SpeedDialLayoutManager layoutManager) {
        SpeedDialLayoutManager layoutManager,
        UpdateSpeedDialAdapterListener updateAdapterListener,
        SupportUiListener<ImmutableList<SpeedDialUiItem>> speedDialLoaderListener) {
      this.activity = activity;
      this.childFragmentManager = childFragmentManager;
      this.contextMenuListener = contextMenuListener;
      this.layoutManager = layoutManager;
      this.updateAdapterListener = updateAdapterListener;
      this.speedDialLoaderListener = speedDialLoaderListener;
    }

    @Override
@@ -384,7 +390,8 @@ public class SpeedDialFragment extends Fragment {
    @Override
    public void showContextMenu(View view, SpeedDialUiItem speedDialUiItem) {
      layoutManager.setScrollEnabled(false);
      contextMenu = ContextMenu.show(activity, view, contextMenuListener, speedDialUiItem);
      contextMenu =
          ContextMenu.show(activity, view, speedDialContextMenuItemListener, speedDialUiItem);
    }

    @Override
@@ -397,12 +404,66 @@ public class SpeedDialFragment extends Fragment {
      }
    }

    public void hideMenu() {
    @Override
    public void onRequestRemove(SpeedDialUiItem speedDialUiItem) {
      speedDialContextMenuItemListener.removeFavoriteContact(speedDialUiItem);
    }

    void hideMenu() {
      if (contextMenu != null) {
        contextMenu.hide();
        contextMenu = null;
      }
    }

    public SpeedDialContextMenuItemListener getSpeedDialContextMenuItemListener() {
      return speedDialContextMenuItemListener;
    }

    class SpeedDialContextMenuItemListener implements ContextMenuItemListener {

      @Override
      public void placeCall(Channel channel) {
        if (channel.technology() == Channel.DUO) {
          Logger.get(activity)
              .logImpression(
                  DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_FAVORITE_CONTACT);
        }
        PreCall.start(
            activity,
            new CallIntentBuilder(channel.number(), CallInitiationType.Type.SPEED_DIAL)
                .setAllowAssistedDial(true)
                .setIsVideoCall(channel.isVideoTechnology())
                .setIsDuoCall(channel.technology() == Channel.DUO));
      }

      @Override
      public void openSmsConversation(String number) {
        activity.startActivity(IntentUtil.getSendSmsIntent(number));
      }

      @Override
      public void removeFavoriteContact(SpeedDialUiItem speedDialUiItem) {
        speedDialLoaderListener.listen(
            activity,
            UiItemLoaderComponent.get(activity)
                .speedDialUiItemMutator()
                .removeSpeedDialUiItem(speedDialUiItem),
            updateAdapterListener::updateAdapter,
            throwable -> {
              throw new RuntimeException(throwable);
            });
      }

      @Override
      public void openContactInfo(SpeedDialUiItem speedDialUiItem) {
        activity.startActivity(
            new Intent(
                Intent.ACTION_VIEW,
                Uri.withAppendedPath(
                    Contacts.CONTENT_URI, String.valueOf(speedDialUiItem.contactId()))));
      }
    }
  }

  private final class SpeedDialSuggestedListener implements SuggestedContactsListener {
@@ -530,63 +591,6 @@ public class SpeedDialFragment extends Fragment {
    }
  }

  private static final class SpeedDialContextMenuItemListener implements ContextMenuItemListener {

    private final FragmentActivity activity;
    private final SupportUiListener<ImmutableList<SpeedDialUiItem>> speedDialLoaderListener;
    private final UpdateSpeedDialAdapterListener updateAdapterListener;

    SpeedDialContextMenuItemListener(
        FragmentActivity activity,
        UpdateSpeedDialAdapterListener updateAdapterListener,
        SupportUiListener<ImmutableList<SpeedDialUiItem>> speedDialLoaderListener) {
      this.activity = activity;
      this.updateAdapterListener = updateAdapterListener;
      this.speedDialLoaderListener = speedDialLoaderListener;
    }

    @Override
    public void placeCall(Channel channel) {
      if (channel.technology() == Channel.DUO) {
        Logger.get(activity)
            .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_FAVORITE_CONTACT);
      }
      PreCall.start(
          activity,
          new CallIntentBuilder(channel.number(), CallInitiationType.Type.SPEED_DIAL)
              .setAllowAssistedDial(true)
              .setIsVideoCall(channel.isVideoTechnology())
              .setIsDuoCall(channel.technology() == Channel.DUO));
    }

    @Override
    public void openSmsConversation(String number) {
      activity.startActivity(IntentUtil.getSendSmsIntent(number));
    }

    @Override
    public void removeFavoriteContact(SpeedDialUiItem speedDialUiItem) {
      speedDialLoaderListener.listen(
          activity,
          UiItemLoaderComponent.get(activity)
              .speedDialUiItemMutator()
              .removeSpeedDialUiItem(speedDialUiItem),
          updateAdapterListener::updateAdapter,
          throwable -> {
            throw new RuntimeException(throwable);
          });
    }

    @Override
    public void openContactInfo(SpeedDialUiItem speedDialUiItem) {
      activity.startActivity(
          new Intent(
              Intent.ACTION_VIEW,
              Uri.withAppendedPath(
                  Contacts.CONTENT_URI, String.valueOf(speedDialUiItem.contactId()))));
    }
  }

  private static final class SpeedDialContactPermissionEmptyViewListener
      implements OnEmptyViewActionButtonClickedListener {

@@ -628,7 +632,7 @@ public class SpeedDialFragment extends Fragment {
  }

  /** Listener for when a SpeedDialUiItem is updated. */
  private class UpdateSpeedDialAdapterListener {
  class UpdateSpeedDialAdapterListener {

    void updateAdapter(ImmutableList<SpeedDialUiItem> speedDialUiItems) {
      onSpeedDialUiItemListLoaded(speedDialUiItems);
Loading