OSDN Git Service

Add implementation of homepage swipe to dismiss.
authorYi-Ling Chuang <emilychuang@google.com>
Tue, 26 Mar 2019 13:16:55 +0000 (21:16 +0800)
committerYi-Ling Chuang <emilychuang@google.com>
Thu, 28 Mar 2019 03:59:45 +0000 (11:59 +0800)
- Only enable swipe for slice full/half card.
- Add isPendingDismiss in ContextualCard to determine if we should show
dismissal view.
- Take out long press feature.

Bug: 126214056
Test: robotests
Change-Id: Ib03e605347b2f50d3c62fcd4f95875a21cc9ef1c

src/com/android/settings/homepage/contextualcards/ContextualCard.java
src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java
src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java
src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java
tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java
tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegateTest.java [new file with mode: 0644]

index 7b8a0c3..ede12fb 100644 (file)
@@ -71,6 +71,7 @@ public class ContextualCard {
     private final Drawable mIconDrawable;
     @LayoutRes
     private final int mViewType;
+    private final boolean mIsPendingDismiss;
 
     public String getName() {
         return mName;
@@ -156,6 +157,10 @@ public class ContextualCard {
         return mViewType;
     }
 
+    public boolean isPendingDismiss() {
+        return mIsPendingDismiss;
+    }
+
     public Builder mutate() {
         return mBuilder;
     }
@@ -181,6 +186,7 @@ public class ContextualCard {
         mIconDrawable = builder.mIconDrawable;
         mIsLargeCard = builder.mIsLargeCard;
         mViewType = builder.mViewType;
+        mIsPendingDismiss = builder.mIsPendingDismiss;
     }
 
     ContextualCard(Cursor c) {
@@ -226,6 +232,8 @@ public class ContextualCard {
         mBuilder.setIconDrawable(mIconDrawable);
         mViewType = getViewTypeByCardType(mCardType);
         mBuilder.setViewType(mViewType);
+        mIsPendingDismiss = false;
+        mBuilder.setIsPendingDismiss(mIsPendingDismiss);
     }
 
     @Override
@@ -277,6 +285,7 @@ public class ContextualCard {
         private boolean mIsLargeCard;
         @LayoutRes
         private int mViewType;
+        private boolean mIsPendingDismiss;
 
         public Builder setName(String name) {
             mName = name;
@@ -373,6 +382,11 @@ public class ContextualCard {
             return this;
         }
 
+        public Builder setIsPendingDismiss(boolean isPendingDismiss) {
+            mIsPendingDismiss = isPendingDismiss;
+            return this;
+        }
+
         public ContextualCard build() {
             return new ContextualCard(this);
         }
index d6df380..7be0e8e 100644 (file)
@@ -28,7 +28,7 @@ import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.settings.homepage.contextualcards.conditional.ConditionContextualCardRenderer;
-import com.android.settings.homepage.contextualcards.slices.SwipeDismissalDelegate.DismissalItemTouchHelperListener;
+import com.android.settings.homepage.contextualcards.slices.SwipeDismissalDelegate;
 import com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer;
 
 import java.util.ArrayList;
@@ -36,7 +36,7 @@ import java.util.List;
 import java.util.Map;
 
 public class ContextualCardsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
-        implements ContextualCardUpdateListener, DismissalItemTouchHelperListener {
+        implements ContextualCardUpdateListener, SwipeDismissalDelegate.Listener {
     static final int SPAN_COUNT = 2;
 
     private static final String TAG = "ContextualCardsAdapter";
@@ -140,6 +140,9 @@ public class ContextualCardsAdapter extends RecyclerView.Adapter<RecyclerView.Vi
 
     @Override
     public void onSwiped(int position) {
-
+        final ContextualCard card = mContextualCards.get(position).mutate()
+                .setIsPendingDismiss(true).build();
+        mContextualCards.set(position, card);
+        notifyItemChanged(position);
     }
 }
index 3ecfcae..590afd2 100644 (file)
@@ -135,23 +135,19 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, Life
                 // Deferred setup is never dismissible.
                 break;
             case VIEW_TYPE_HALF_WIDTH:
-                initDismissalActions(holder, card, R.id.content);
+                initDismissalActions(holder, card);
                 break;
             default:
-                initDismissalActions(holder, card, R.id.slice_view);
+                initDismissalActions(holder, card);
         }
-    }
 
-    private void initDismissalActions(RecyclerView.ViewHolder holder, ContextualCard card,
-            int initialViewId) {
-        // initialView is the first view in the ViewFlipper.
-        final View initialView = holder.itemView.findViewById(initialViewId);
-        initialView.setOnLongClickListener(v -> {
+        if (card.isPendingDismiss()) {
             flipCardToDismissalView(holder);
             mFlippedCardSet.add(holder);
-            return true;
-        });
+        }
+    }
 
+    private void initDismissalActions(RecyclerView.ViewHolder holder, ContextualCard card) {
         final Button btnKeep = holder.itemView.findViewById(R.id.keep);
         btnKeep.setOnClickListener(v -> {
             mFlippedCardSet.remove(holder);
index a4186b0..121d4aa 100644 (file)
@@ -18,31 +18,62 @@ package com.android.settings.homepage.contextualcards.slices;
 
 import android.content.Context;
 import android.graphics.Canvas;
+import android.widget.ViewFlipper;
 
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.ItemTouchHelper;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.settings.R;
+import com.android.settings.homepage.contextualcards.ContextualCard;
+
 public class SwipeDismissalDelegate extends ItemTouchHelper.Callback {
 
     private static final String TAG = "DismissItemTouchHelper";
 
-    public interface DismissalItemTouchHelperListener {
+    public interface Listener {
         void onSwiped(int position);
     }
 
     private final Context mContext;
-    private final DismissalItemTouchHelperListener mListener;
+    private final SwipeDismissalDelegate.Listener mListener;
 
-    public SwipeDismissalDelegate(Context context, DismissalItemTouchHelperListener listener) {
+    public SwipeDismissalDelegate(Context context, SwipeDismissalDelegate.Listener listener) {
         mContext = context;
         mListener = listener;
     }
 
+    /**
+     * Determine whether the ability to drag or swipe should be enabled or not.
+     *
+     * Only allow swipe on {@link ContextualCard} built with view type
+     * {@link SliceContextualCardRenderer#VIEW_TYPE_FULL_WIDTH} or
+     * {@link SliceContextualCardRenderer#VIEW_TYPE_HALF_WIDTH}.
+     *
+     * When the dismissal view is displayed, the swipe will also be disabled.
+     */
     @Override
     public int getMovementFlags(@NonNull RecyclerView recyclerView,
             @NonNull RecyclerView.ViewHolder viewHolder) {
-        return 0;
+        switch (viewHolder.getItemViewType()) {
+            case SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH:
+            case SliceContextualCardRenderer.VIEW_TYPE_HALF_WIDTH:
+                //TODO(b/129438972): Convert this to a regular view.
+                final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.view_flipper);
+
+                // As we are using ViewFlipper to switch between the initial view and
+                // dismissal view, here we are making sure the current displayed view is the
+                // initial view of either slice full card or half card, and only allow swipe on
+                // these two types.
+                if (viewFlipper.getCurrentView().getId() != getInitialViewId(viewHolder)) {
+                    // Disable swiping when we are in the dismissal view
+                    return 0;
+                }
+                return makeMovementFlags(0 /*dragFlags*/,
+                        ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT /*swipeFlags*/);
+            default:
+                return 0;
+        }
     }
 
     @Override
@@ -63,4 +94,11 @@ public class SwipeDismissalDelegate extends ItemTouchHelper.Callback {
             boolean isCurrentlyActive) {
         super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
     }
+
+    private int getInitialViewId(RecyclerView.ViewHolder viewHolder) {
+        if (viewHolder.getItemViewType() == SliceContextualCardRenderer.VIEW_TYPE_HALF_WIDTH) {
+            return R.id.content;
+        }
+        return R.id.slice_view;
+    }
 }
\ No newline at end of file
index e08d845..a53ade2 100644 (file)
 
 package com.android.settings.homepage.contextualcards.slices;
 
-import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_DEFERRED_SETUP;
 import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
@@ -118,34 +116,25 @@ public class SliceContextualCardRendererTest {
     }
 
     @Test
-    public void longClick_shouldFlipCard() {
+    public void bindView_isPendingDismiss_shouldFlipToDismissalView() {
         final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
-        final View card = viewHolder.itemView.findViewById(R.id.slice_view);
         final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.view_flipper);
         final View dismissalView = viewHolder.itemView.findViewById(R.id.dismissal_view);
-        mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI));
+        final ContextualCard card = buildContextualCard(
+                TEST_SLICE_URI).mutate().setIsPendingDismiss(true).build();
 
-        card.performLongClick();
+        mRenderer.bindView(viewHolder, card);
 
         assertThat(viewFlipper.getCurrentView()).isEqualTo(dismissalView);
     }
 
     @Test
-    public void longClick_deferredSetupCard_shouldNotBeClickable() {
-        final RecyclerView.ViewHolder viewHolder = getDeferredSetupViewHolder();
-        final View contentView = viewHolder.itemView.findViewById(R.id.content);
-        mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI));
-
-        assertThat(contentView.isLongClickable()).isFalse();
-    }
-
-    @Test
-    public void longClick_shouldAddViewHolderToSet() {
+    public void bindView_isPendingDismiss_shouldAddViewHolderToSet() {
         final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
-        final View card = viewHolder.itemView.findViewById(R.id.slice_view);
-        mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI));
+        final ContextualCard card = buildContextualCard(
+                TEST_SLICE_URI).mutate().setIsPendingDismiss(true).build();
 
-        card.performLongClick();
+        mRenderer.bindView(viewHolder, card);
 
         assertThat(mRenderer.mFlippedCardSet).contains(viewHolder);
     }
@@ -232,18 +221,6 @@ public class SliceContextualCardRendererTest {
         return mRenderer.createViewHolder(view, VIEW_TYPE_FULL_WIDTH);
     }
 
-    private RecyclerView.ViewHolder getDeferredSetupViewHolder() {
-        final RecyclerView recyclerView = new RecyclerView(mActivity);
-        recyclerView.setLayoutManager(new LinearLayoutManager(mActivity));
-        final View view = LayoutInflater.from(mActivity).inflate(VIEW_TYPE_DEFERRED_SETUP,
-                recyclerView, false);
-        final RecyclerView.ViewHolder viewHolder = spy(
-                mRenderer.createViewHolder(view, VIEW_TYPE_DEFERRED_SETUP));
-        doReturn(VIEW_TYPE_DEFERRED_SETUP).when(viewHolder).getItemViewType();
-
-        return viewHolder;
-    }
-
     private ContextualCard buildContextualCard(Uri sliceUri) {
         return new ContextualCard.Builder()
                 .setName("test_name")
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegateTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegateTest.java
new file mode 100644 (file)
index 0000000..00b7815
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 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.settings.homepage.contextualcards.slices;
+
+import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_DEFERRED_SETUP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.Activity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ViewFlipper;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settings.R;
+import com.android.settings.homepage.contextualcards.conditional.ConditionContextualCardRenderer;
+import com.android.settings.homepage.contextualcards.conditional.ConditionContextualCardRenderer.ConditionalCardHolder;
+import com.android.settings.homepage.contextualcards.slices.SliceDeferredSetupCardRendererHelper.DeferredSetupCardViewHolder;
+import com.android.settings.homepage.contextualcards.slices.SliceFullCardRendererHelper.SliceViewHolder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.android.controller.ActivityController;
+
+@RunWith(RobolectricTestRunner.class)
+public class SwipeDismissalDelegateTest {
+
+    @Mock
+    private SwipeDismissalDelegate.Listener mDismissalDelegateListener;
+
+    private Activity mActivity;
+    private RecyclerView mRecyclerView;
+    private SwipeDismissalDelegate mDismissalDelegate;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        final ActivityController<Activity> activityController = Robolectric.buildActivity(
+                Activity.class);
+        mActivity = activityController.get();
+        mActivity.setTheme(R.style.Theme_Settings_Home);
+        activityController.create();
+        mRecyclerView = new RecyclerView(mActivity);
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity));
+        mDismissalDelegate = new SwipeDismissalDelegate(mActivity, mDismissalDelegateListener);
+    }
+
+    @Test
+    public void getMovementFlags_conditionalViewHolder_shouldDisableSwipe() {
+        assertThat(mDismissalDelegate.getMovementFlags(mRecyclerView, getConditionalViewHolder()))
+                .isEqualTo(0);
+    }
+
+    @Test
+    public void getMovementFlags_deferredSetupViewHolder_shouldDisableSwipe() {
+        assertThat(mDismissalDelegate.getMovementFlags(mRecyclerView, getDeferredSetupViewHolder()))
+                .isEqualTo(0);
+    }
+
+    @Test
+    public void getMovementFlags_dismissalView_shouldDisableSwipe() {
+        final RecyclerView.ViewHolder holder = getSliceViewHolder();
+        final ViewFlipper viewFlipper = holder.itemView.findViewById(R.id.view_flipper);
+        viewFlipper.showNext();
+        final View dismissalView = holder.itemView.findViewById(R.id.dismissal_view);
+
+        assertThat(viewFlipper.getCurrentView()).isEqualTo(dismissalView);
+        assertThat(mDismissalDelegate.getMovementFlags(mRecyclerView, holder)).isEqualTo(0);
+    }
+
+    @Test
+    public void getMovementFlags_SliceViewHolder_shouldEnableSwipe() {
+        final RecyclerView.ViewHolder holder = getSliceViewHolder();
+        final ViewFlipper viewFlipper = holder.itemView.findViewById(R.id.view_flipper);
+        viewFlipper.setDisplayedChild(0);
+        final View sliceView = holder.itemView.findViewById(R.id.slice_view);
+
+        assertThat(viewFlipper.getCurrentView()).isEqualTo(sliceView);
+        assertThat(mDismissalDelegate.getMovementFlags(mRecyclerView, getSliceViewHolder()))
+                .isNotEqualTo(0);
+    }
+
+    @Test
+    public void onSwipe_shouldNotifyListener() {
+        mDismissalDelegate.onSwiped(getSliceViewHolder(), 1);
+
+        verify(mDismissalDelegateListener).onSwiped(anyInt());
+    }
+
+    private RecyclerView.ViewHolder getSliceViewHolder() {
+        final View view = LayoutInflater.from(mActivity)
+                .inflate(SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH, mRecyclerView, false);
+        final RecyclerView.ViewHolder viewHolder = spy(new SliceViewHolder(view));
+        doReturn(SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH).when(
+                viewHolder).getItemViewType();
+
+        return viewHolder;
+    }
+
+    private RecyclerView.ViewHolder getConditionalViewHolder() {
+        final View view = LayoutInflater.from(mActivity)
+                .inflate(ConditionContextualCardRenderer.VIEW_TYPE_FULL_WIDTH, mRecyclerView,
+                        false);
+        final RecyclerView.ViewHolder viewHolder = spy(new ConditionalCardHolder(view));
+        doReturn(ConditionContextualCardRenderer.VIEW_TYPE_FULL_WIDTH).when(
+                viewHolder).getItemViewType();
+
+        return viewHolder;
+    }
+
+    private RecyclerView.ViewHolder getDeferredSetupViewHolder() {
+        final View view = LayoutInflater.from(mActivity)
+                .inflate(VIEW_TYPE_DEFERRED_SETUP, mRecyclerView, false);
+        final RecyclerView.ViewHolder viewHolder = spy(new DeferredSetupCardViewHolder(view));
+        doReturn(VIEW_TYPE_DEFERRED_SETUP).when(viewHolder).getItemViewType();
+
+        return viewHolder;
+    }
+}