From 406de13ab4326bbedae0262709a004da2211d04c Mon Sep 17 00:00:00 2001 From: yueg Date: Tue, 19 Jun 2018 16:54:38 -0700 Subject: Drag favorite to remove Test: RemoveViewHolderTest, SpeedDialAdapterTest PiperOrigin-RevId: 201266033 Change-Id: Ie7ed9bac8ad9c7bbc35c351409b629e3fbad3de8 --- .../dialer/speeddial/FavoritesViewHolder.java | 15 +++ .../android/dialer/speeddial/RemoveViewHolder.java | 49 ++++++++ .../android/dialer/speeddial/SpeedDialAdapter.java | 128 +++++++++++++++++-- .../dialer/speeddial/SpeedDialFragment.java | 138 +++++++++++---------- .../SpeedDialItemTouchHelperCallback.java | 62 +++++++++ .../res/layout/favorite_remove_view_layout.xml | 49 ++++++++ .../speeddial/res/layout/fragment_speed_dial.xml | 7 +- .../android/dialer/speeddial/res/values/dimens.xml | 2 + .../dialer/speeddial/res/values/strings.xml | 3 + 9 files changed, 372 insertions(+), 81 deletions(-) create mode 100644 java/com/android/dialer/speeddial/RemoveViewHolder.java create mode 100644 java/com/android/dialer/speeddial/res/layout/favorite_remove_view_layout.xml (limited to 'java/com/android/dialer/speeddial') diff --git a/java/com/android/dialer/speeddial/FavoritesViewHolder.java b/java/com/android/dialer/speeddial/FavoritesViewHolder.java index f06672d8d..600c8c734 100644 --- a/java/com/android/dialer/speeddial/FavoritesViewHolder.java +++ b/java/com/android/dialer/speeddial/FavoritesViewHolder.java @@ -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); } } diff --git a/java/com/android/dialer/speeddial/RemoveViewHolder.java b/java/com/android/dialer/speeddial/RemoveViewHolder.java new file mode 100644 index 000000000..998e1ae2f --- /dev/null +++ b/java/com/android/dialer/speeddial/RemoveViewHolder.java @@ -0,0 +1,49 @@ +/* + * 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 + } +} diff --git a/java/com/android/dialer/speeddial/SpeedDialAdapter.java b/java/com/android/dialer/speeddial/SpeedDialAdapter.java index a382b1a6b..ff52a0745 100644 --- a/java/com/android/dialer/speeddial/SpeedDialAdapter.java +++ b/java/com/android/dialer/speeddial/SpeedDialAdapter.java @@ -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 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> speedDialLoaderListener; + + private final SpeedDialContextMenuItemListener speedDialContextMenuItemListener = + new SpeedDialContextMenuItemListener(); private ContextMenu contextMenu; SpeedDialFavoritesListener( FragmentActivity activity, FragmentManager childFragmentManager, - ContextMenuItemListener contextMenuListener, - SpeedDialLayoutManager layoutManager) { + SpeedDialLayoutManager layoutManager, + UpdateSpeedDialAdapterListener updateAdapterListener, + SupportUiListener> 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> speedDialLoaderListener; - private final UpdateSpeedDialAdapterListener updateAdapterListener; - - SpeedDialContextMenuItemListener( - FragmentActivity activity, - UpdateSpeedDialAdapterListener updateAdapterListener, - SupportUiListener> 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 speedDialUiItems) { onSpeedDialUiItemListLoaded(speedDialUiItems); diff --git a/java/com/android/dialer/speeddial/draghelper/SpeedDialItemTouchHelperCallback.java b/java/com/android/dialer/speeddial/draghelper/SpeedDialItemTouchHelperCallback.java index d1d9f478b..fc963a1a3 100644 --- a/java/com/android/dialer/speeddial/draghelper/SpeedDialItemTouchHelperCallback.java +++ b/java/com/android/dialer/speeddial/draghelper/SpeedDialItemTouchHelperCallback.java @@ -16,7 +16,9 @@ package com.android.dialer.speeddial.draghelper; +import android.graphics.Canvas; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.support.v7.widget.helper.ItemTouchHelper; @@ -26,6 +28,12 @@ public class SpeedDialItemTouchHelperCallback extends ItemTouchHelper.Callback { private final ItemTouchHelperAdapter adapter; + // When dragged item is in removeView, onMove() and onChildDraw() are called in turn. This + // behavior changes when dragged item entering/leaving removeView. The boolean field + // movedOverRemoveView is for onMove() and onChildDraw() to flip. + private boolean movedOverRemoveView; + private boolean inRemoveView; + public SpeedDialItemTouchHelperCallback(ItemTouchHelperAdapter adapter) { this.adapter = adapter; } @@ -64,10 +72,56 @@ public class SpeedDialItemTouchHelperCallback extends ItemTouchHelper.Callback { @NonNull RecyclerView recyclerView, @NonNull ViewHolder viewHolder, @NonNull ViewHolder target) { + if (target.getItemViewType() == 0) { // 0 for RowType.REMOVE_VIEW + movedOverRemoveView = true; + if (!inRemoveView) { + // onMove() first called + adapter.enterRemoveView(); + inRemoveView = true; + } + return false; + } else if (inRemoveView) { + // Move out of removeView fast + inRemoveView = false; + movedOverRemoveView = false; + adapter.leaveRemoveView(); + } adapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition()); return true; } + @Override + public void onChildDraw( + @NonNull Canvas canvas, + @NonNull RecyclerView recyclerView, + @NonNull ViewHolder viewHolder, + float dx, + float dy, + int i, + boolean isCurrentlyActive) { + if (inRemoveView) { + if (!isCurrentlyActive) { + // View animating back to its original state, which means drop in this case + inRemoveView = false; + adapter.dropOnRemoveView(viewHolder); + } + if (!movedOverRemoveView) { + // when the view is over a droppable target, onMove() will be called before onChildDraw() + // thus if onMove() is not called, it is not over a droppable target. + inRemoveView = false; + adapter.leaveRemoveView(); + } + } + movedOverRemoveView = false; + super.onChildDraw(canvas, recyclerView, viewHolder, dx, dy, i, isCurrentlyActive); + } + + @Override + public void onSelectedChanged(@Nullable ViewHolder viewHolder, int actionState) { + super.onSelectedChanged(viewHolder, actionState); + adapter.onSelectedChanged(viewHolder, actionState); + } + @Override public void onSwiped(@NonNull ViewHolder viewHolder, int direction) { // No-op since we don't support swiping @@ -79,5 +133,13 @@ public class SpeedDialItemTouchHelperCallback extends ItemTouchHelper.Callback { void onItemMove(int fromPosition, int toPosition); boolean canDropOver(ViewHolder target); + + void onSelectedChanged(@Nullable ViewHolder viewHolder, int actionState); + + void enterRemoveView(); + + void leaveRemoveView(); + + void dropOnRemoveView(ViewHolder fromViewHolder); } } diff --git a/java/com/android/dialer/speeddial/res/layout/favorite_remove_view_layout.xml b/java/com/android/dialer/speeddial/res/layout/favorite_remove_view_layout.xml new file mode 100644 index 000000000..825658c37 --- /dev/null +++ b/java/com/android/dialer/speeddial/res/layout/favorite_remove_view_layout.xml @@ -0,0 +1,49 @@ + + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/dialer/speeddial/res/layout/fragment_speed_dial.xml b/java/com/android/dialer/speeddial/res/layout/fragment_speed_dial.xml index e289bb794..472f9e951 100644 --- a/java/com/android/dialer/speeddial/res/layout/fragment_speed_dial.xml +++ b/java/com/android/dialer/speeddial/res/layout/fragment_speed_dial.xml @@ -17,14 +17,17 @@ + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false"> -24dp 16dp 280dp + 64dp + -64dp \ No newline at end of file diff --git a/java/com/android/dialer/speeddial/res/values/strings.xml b/java/com/android/dialer/speeddial/res/values/strings.xml index 7f8fed51f..397ec8f5e 100644 --- a/java/com/android/dialer/speeddial/res/values/strings.xml +++ b/java/com/android/dialer/speeddial/res/values/strings.xml @@ -71,4 +71,7 @@ Add favorite + + + Remove from favorites \ No newline at end of file -- cgit v1.2.3