Loading src/com/android/settings/homepage/contextualcards/ContextualCard.java +14 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -156,6 +157,10 @@ public class ContextualCard { return mViewType; } public boolean isPendingDismiss() { return mIsPendingDismiss; } public Builder mutate() { return mBuilder; } Loading @@ -181,6 +186,7 @@ public class ContextualCard { mIconDrawable = builder.mIconDrawable; mIsLargeCard = builder.mIsLargeCard; mViewType = builder.mViewType; mIsPendingDismiss = builder.mIsPendingDismiss; } ContextualCard(Cursor c) { Loading Loading @@ -226,6 +232,8 @@ public class ContextualCard { mBuilder.setIconDrawable(mIconDrawable); mViewType = getViewTypeByCardType(mCardType); mBuilder.setViewType(mViewType); mIsPendingDismiss = false; mBuilder.setIsPendingDismiss(mIsPendingDismiss); } @Override Loading Loading @@ -277,6 +285,7 @@ public class ContextualCard { private boolean mIsLargeCard; @LayoutRes private int mViewType; private boolean mIsPendingDismiss; public Builder setName(String name) { mName = name; Loading Loading @@ -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); } Loading src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java +6 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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); } } src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java +6 −10 Original line number Diff line number Diff line Loading @@ -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); Loading src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java +42 −4 Original line number Diff line number Diff line Loading @@ -18,32 +18,63 @@ 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) { 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 public boolean onMove(@NonNull RecyclerView recyclerView, Loading @@ -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 tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java +8 −31 Original line number Diff line number Diff line Loading @@ -16,13 +16,11 @@ 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; Loading Loading @@ -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); } Loading Loading @@ -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") Loading Loading
src/com/android/settings/homepage/contextualcards/ContextualCard.java +14 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -156,6 +157,10 @@ public class ContextualCard { return mViewType; } public boolean isPendingDismiss() { return mIsPendingDismiss; } public Builder mutate() { return mBuilder; } Loading @@ -181,6 +186,7 @@ public class ContextualCard { mIconDrawable = builder.mIconDrawable; mIsLargeCard = builder.mIsLargeCard; mViewType = builder.mViewType; mIsPendingDismiss = builder.mIsPendingDismiss; } ContextualCard(Cursor c) { Loading Loading @@ -226,6 +232,8 @@ public class ContextualCard { mBuilder.setIconDrawable(mIconDrawable); mViewType = getViewTypeByCardType(mCardType); mBuilder.setViewType(mViewType); mIsPendingDismiss = false; mBuilder.setIsPendingDismiss(mIsPendingDismiss); } @Override Loading Loading @@ -277,6 +285,7 @@ public class ContextualCard { private boolean mIsLargeCard; @LayoutRes private int mViewType; private boolean mIsPendingDismiss; public Builder setName(String name) { mName = name; Loading Loading @@ -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); } Loading
src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java +6 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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); } }
src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java +6 −10 Original line number Diff line number Diff line Loading @@ -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); Loading
src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java +42 −4 Original line number Diff line number Diff line Loading @@ -18,32 +18,63 @@ 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) { 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 public boolean onMove(@NonNull RecyclerView recyclerView, Loading @@ -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
tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java +8 −31 Original line number Diff line number Diff line Loading @@ -16,13 +16,11 @@ 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; Loading Loading @@ -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); } Loading Loading @@ -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") Loading