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

Commit d94c00d6 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Implement drag to remove contacts in SpeedDialFragment."

parents 91004ecc 5621909c
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -81,6 +81,10 @@ public class ContextMenu extends LinearLayout {
    }
  }

  public boolean isVisible() {
    return getVisibility() == View.VISIBLE;
  }

  /** Listener to report user clicks on menu items. */
  public interface ContextMenuItemListener {

+19 −5
Original line number Diff line number Diff line
@@ -19,9 +19,11 @@ package com.android.dialer.speeddial;
import android.content.Context;
import android.provider.ContactsContract.Contacts;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewConfiguration;
import android.widget.FrameLayout;
import android.widget.QuickContactBadge;
import android.widget.TextView;
@@ -29,12 +31,14 @@ import com.android.dialer.common.Assert;
import com.android.dialer.glidephotomanager.GlidePhotoManagerComponent;
import com.android.dialer.glidephotomanager.PhotoInfo;
import com.android.dialer.speeddial.database.SpeedDialEntry.Channel;
import com.android.dialer.speeddial.draghelper.SpeedDialFavoritesViewHolderOnTouchListener;
import com.android.dialer.speeddial.draghelper.SpeedDialFavoritesViewHolderOnTouchListener.OnTouchFinishCallback;
import com.android.dialer.speeddial.loader.SpeedDialUiItem;
import java.util.List;

/** ViewHolder for starred/favorite contacts in {@link SpeedDialFragment}. */
public class FavoritesViewHolder extends RecyclerView.ViewHolder
    implements OnClickListener, OnLongClickListener {
    implements OnClickListener, OnLongClickListener, OnTouchFinishCallback {

  private final FavoriteContactsListener listener;

@@ -45,7 +49,7 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder

  private SpeedDialUiItem speedDialUiItem;

  public FavoritesViewHolder(View view, FavoriteContactsListener listener) {
  public FavoritesViewHolder(View view, ItemTouchHelper helper, FavoriteContactsListener listener) {
    super(view);
    photoView = view.findViewById(R.id.avatar);
    nameView = view.findViewById(R.id.name);
@@ -53,6 +57,9 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder
    videoCallIcon = view.findViewById(R.id.video_call_container);
    view.setOnClickListener(this);
    view.setOnLongClickListener(this);
    view.setOnTouchListener(
        new SpeedDialFavoritesViewHolderOnTouchListener(
            ViewConfiguration.get(view.getContext()), helper, this, this));
    photoView.setClickable(false);
    this.listener = listener;
  }
@@ -96,12 +103,16 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder

  @Override
  public boolean onLongClick(View view) {
    // TODO(calderwoodra): implement drag and drop logic
    // TODO(calderwoodra): add bounce/sin wave scale animation
    listener.onLongClick(photoView, speedDialUiItem);
    listener.showContextMenu(photoView, speedDialUiItem);
    return true;
  }

  @Override
  public void onTouchFinished(boolean closeContextMenu) {
    listener.onTouchFinished(closeContextMenu);
  }

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

@@ -112,6 +123,9 @@ public class FavoritesViewHolder extends RecyclerView.ViewHolder
    void onClick(Channel channel);

    /** Called when the user long clicks on a favorite contact. */
    void onLongClick(View view, SpeedDialUiItem speedDialUiItem);
    void showContextMenu(View view, SpeedDialUiItem speedDialUiItem);

    /** Called when the user is no longer touching the favorite contact. */
    void onTouchFinished(boolean closeContextMenu);
  }
}
+49 −27
Original line number Diff line number Diff line
@@ -21,12 +21,10 @@ import android.content.Context;
import android.os.Build.VERSION_CODES;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;
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;
@@ -34,10 +32,12 @@ 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.SuggestionViewHolder.SuggestedContactsListener;
import com.android.dialer.speeddial.draghelper.SpeedDialItemTouchHelperCallback.ItemTouchHelperAdapter;
import com.android.dialer.speeddial.loader.SpeedDialUiItem;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

@@ -55,7 +55,8 @@ import java.util.Map;
 */
@SuppressWarnings("AndroidApiChecker")
@TargetApi(VERSION_CODES.N)
public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
    implements ItemTouchHelperAdapter {

  @Retention(RetentionPolicy.SOURCE)
  @IntDef({RowType.STARRED_HEADER, RowType.SUGGESTION_HEADER, RowType.STARRED, RowType.SUGGESTION})
@@ -74,6 +75,9 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi
  private final Map<Integer, Integer> positionToRowTypeMap = new ArrayMap<>();
  private List<SpeedDialUiItem> speedDialUiItems;

  // Needed for FavoriteViewHolder
  private ItemTouchHelper itemTouchHelper;

  public SpeedDialAdapter(
      Context context,
      FavoriteContactsListener favoritesListener,
@@ -97,7 +101,9 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi
    switch (viewType) {
      case RowType.STARRED:
        return new FavoritesViewHolder(
            inflater.inflate(R.layout.favorite_item_layout, parent, false), favoritesListener);
            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);
@@ -162,20 +168,10 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi
    }
  }

  /* package-private */ LayoutManager getLayoutManager(Context context) {
    GridLayoutManager layoutManager = new GridLayoutManager(context, 3 /* spanCount */);
    layoutManager.setSpanSizeLookup(
        new SpanSizeLookup() {
  public SpanSizeLookup getSpanSizeLookup() {
    return new SpanSizeLookup() {
      @Override
      public int getSpanSize(int position) {
            return SpeedDialAdapter.this.getSpanSize(position);
          }
        });
    return layoutManager;
  }

  @VisibleForTesting
  int getSpanSize(int position) {
        switch (getItemViewType(position)) {
          case RowType.SUGGESTION:
          case RowType.STARRED_HEADER:
@@ -188,4 +184,30 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi
                "Invalid row type: " + positionToRowTypeMap.get(position));
        }
      }
    };
  }

  @Override
  public void onItemMove(int fromPosition, int toPosition) {
    if (fromPosition < toPosition) {
      for (int i = fromPosition; i < toPosition && i < speedDialUiItems.size() - 1; i++) {
        Collections.swap(speedDialUiItems, i, i + 1);
      }
    } else {
      for (int i = fromPosition - 1; i > toPosition; i--) {
        Collections.swap(speedDialUiItems, i, i - 1);
      }
    }
    // TODO(calderwoodra): store pinned positions
    notifyItemMoved(fromPosition, toPosition);
  }

  @Override
  public boolean canDropOver(ViewHolder target) {
    return target instanceof FavoritesViewHolder;
  }

  public void setItemTouchHelper(ItemTouchHelper itemTouchHelper) {
    this.itemTouchHelper = itemTouchHelper;
  }
}
+36 −5
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -36,6 +37,8 @@ import com.android.dialer.speeddial.FavoritesViewHolder.FavoriteContactsListener
import com.android.dialer.speeddial.HeaderViewHolder.SpeedDialHeaderListener;
import com.android.dialer.speeddial.SuggestionViewHolder.SuggestedContactsListener;
import com.android.dialer.speeddial.database.SpeedDialEntry.Channel;
import com.android.dialer.speeddial.draghelper.SpeedDialItemTouchHelperCallback;
import com.android.dialer.speeddial.draghelper.SpeedDialLayoutManager;
import com.android.dialer.speeddial.loader.SpeedDialUiItem;
import com.android.dialer.speeddial.loader.UiItemLoaderComponent;
import com.google.common.collect.ImmutableList;
@@ -60,8 +63,10 @@ public class SpeedDialFragment extends Fragment {
  private View rootLayout;
  private ContextMenu contextMenu;
  private FrameLayout contextMenuBackground;
  private SpeedDialAdapter adapter;
  private ContextMenuItemListener contextMenuItemListener;

  private SpeedDialAdapter adapter;
  private SpeedDialLayoutManager layoutManager;
  private SupportUiListener<ImmutableList<SpeedDialUiItem>> speedDialLoaderListener;

  public static SpeedDialFragment newInstance() {
@@ -74,13 +79,23 @@ public class SpeedDialFragment extends Fragment {
      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    LogUtil.enterBlock("SpeedDialFragment.onCreateView");
    rootLayout = inflater.inflate(R.layout.fragment_speed_dial, container, false);
    RecyclerView recyclerView = rootLayout.findViewById(R.id.speed_dial_recycler_view);

    // Setup our RecyclerView
    RecyclerView recyclerView = rootLayout.findViewById(R.id.speed_dial_recycler_view);
    adapter =
        new SpeedDialAdapter(getContext(), favoritesListener, suggestedListener, headerListener);
    recyclerView.setLayoutManager(adapter.getLayoutManager(getContext()));
    layoutManager = new SpeedDialLayoutManager(getContext(), 3 /* spanCount */);
    layoutManager.setSpanSizeLookup(adapter.getSpanSizeLookup());
    recyclerView.setLayoutManager(layoutManager);
    recyclerView.setAdapter(adapter);

    // Setup drag and drop touch helper
    ItemTouchHelper.Callback callback = new SpeedDialItemTouchHelperCallback(adapter);
    ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
    touchHelper.attachToRecyclerView(recyclerView);
    adapter.setItemTouchHelper(touchHelper);

    // Setup favorite contact context menu
    contextMenu = rootLayout.findViewById(R.id.favorite_contact_context_menu);
    contextMenuBackground = rootLayout.findViewById(R.id.context_menu_background);
    contextMenuBackground.setOnClickListener(
@@ -141,10 +156,26 @@ public class SpeedDialFragment extends Fragment {
    }

    @Override
    public void onLongClick(View view, SpeedDialUiItem speedDialUiItem) {
      contextMenuBackground.setVisibility(View.VISIBLE);
    public void showContextMenu(View view, SpeedDialUiItem speedDialUiItem) {
      layoutManager.setScrollEnabled(false);
      contextMenu.showMenu(rootLayout, view, speedDialUiItem, contextMenuItemListener);
    }

    @Override
    public void onTouchFinished(boolean closeContextMenu) {
      layoutManager.setScrollEnabled(true);

      if (closeContextMenu) {
        contextMenu.hideMenu();
      } else if (contextMenu.isVisible()) {
        // If we're showing the context menu, show this background surface so that we can intercept
        // touch events to close the menu
        // Note: We call this in onTouchFinished because if we show the background before the user
        // is done, they might try to drag the view and but won't be able to because this view would
        // intercept all of the touch events.
        contextMenuBackground.setVisibility(View.VISIBLE);
      }
    }
  }

  private final class SpeedDialSuggestedListener implements SuggestedContactsListener {
+107 −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.draghelper;

import android.support.v7.widget.RecyclerView.ViewHolder;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import com.android.dialer.common.Assert;

/** OnTouchListener for the {@link com.android.dialer.speeddial.FavoritesViewHolder}. */
public class SpeedDialFavoritesViewHolderOnTouchListener implements OnTouchListener {

  private final ViewConfiguration configuration;
  private final ItemTouchHelper itemTouchHelper;
  private final ViewHolder viewHolder;
  private final OnTouchFinishCallback onTouchFinishCallback;

  private boolean hasPerformedLongClick;
  private float startX;
  private float startY;

  public SpeedDialFavoritesViewHolderOnTouchListener(
      ViewConfiguration configuration,
      ItemTouchHelper itemTouchHelper,
      ViewHolder viewHolder,
      OnTouchFinishCallback onTouchFinishCallback) {
    this.configuration = configuration;
    this.itemTouchHelper = itemTouchHelper;
    this.viewHolder = viewHolder;
    this.onTouchFinishCallback = onTouchFinishCallback;
  }

  @Override
  public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        startX = event.getX();
        startY = event.getY();
        return true;
      case MotionEvent.ACTION_MOVE:
        // If the user has long clicked the view
        if (event.getEventTime() - event.getDownTime() > ViewConfiguration.getLongPressTimeout()) {
          // Perform long click if we haven't already
          if (!hasPerformedLongClick) {
            v.performLongClick();
            v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
            hasPerformedLongClick = true;
          } else if (moveEventExceedsTouchSlop(event)) {
            itemTouchHelper.startDrag(viewHolder);
            onTouchFinishCallback.onTouchFinished(true);
          }
        }
        return true;
      case MotionEvent.ACTION_UP:
        if (event.getEventTime() - event.getDownTime() < ViewConfiguration.getLongPressTimeout()) {
          v.performClick();
        }
        // fallthrough
      case MotionEvent.ACTION_CANCEL:
        hasPerformedLongClick = false;
        onTouchFinishCallback.onTouchFinished(false);
        return true;
      default:
        return false;
    }
  }

  private boolean moveEventExceedsTouchSlop(MotionEvent event) {
    Assert.checkArgument(event.getAction() == MotionEvent.ACTION_MOVE);
    if (event.getHistorySize() <= 0) {
      return false;
    }

    return Math.abs(startX - event.getX()) > configuration.getScaledTouchSlop()
        || Math.abs(startY - event.getY()) > configuration.getScaledTouchSlop();
  }

  /** Callback to listen for on touch events ending. */
  public interface OnTouchFinishCallback {

    /**
     * Called when the user stops touching the view.
     *
     * @see MotionEvent#ACTION_UP
     * @see MotionEvent#ACTION_CANCEL
     */
    void onTouchFinished(boolean closeContextMenu);
  }
}
Loading