Merge "Reset dismissal UI to the baseline when exiting the page."

This commit is contained in:
TreeHugger Robot
2018-12-05 18:32:51 +00:00
committed by Android (Google) Code Review
3 changed files with 95 additions and 25 deletions

View File

@@ -69,9 +69,9 @@ public class SliceContextualCardController implements ContextualCardController {
dbHelper.markContextualCardAsDismissed(mContext, card.getName()); dbHelper.markContextualCardAsDismissed(mContext, card.getName());
}); });
showFeedbackDialog(card); showFeedbackDialog(card);
final ContextualCardFeatureProvider contexualCardFeatureProvider = final ContextualCardFeatureProvider contextualCardFeatureProvider =
FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(); FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider();
contexualCardFeatureProvider.logContextualCardDismiss(mContext, card); contextualCardFeatureProvider.logContextualCardDismiss(mContext, card);
} }
@Override @Override

View File

@@ -28,8 +28,11 @@ import android.widget.ViewFlipper;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.slice.Slice; import androidx.slice.Slice;
import androidx.slice.SliceItem; import androidx.slice.SliceItem;
@@ -51,13 +54,15 @@ import java.util.Set;
* Card renderer for {@link ContextualCard} built as slices. * Card renderer for {@link ContextualCard} built as slices.
*/ */
public class SliceContextualCardRenderer implements ContextualCardRenderer, public class SliceContextualCardRenderer implements ContextualCardRenderer,
SliceView.OnSliceActionListener { SliceView.OnSliceActionListener, LifecycleObserver {
public static final int VIEW_TYPE = R.layout.homepage_slice_tile; public static final int VIEW_TYPE = R.layout.homepage_slice_tile;
private static final String TAG = "SliceCardRenderer"; private static final String TAG = "SliceCardRenderer";
@VisibleForTesting @VisibleForTesting
final Map<String, LiveData<Slice>> mSliceLiveDataMap; final Map<String, LiveData<Slice>> mSliceLiveDataMap;
@VisibleForTesting
final Set<SliceViewHolder> mFlippedCardSet;
private final Context mContext; private final Context mContext;
private final LifecycleOwner mLifecycleOwner; private final LifecycleOwner mLifecycleOwner;
@@ -71,6 +76,8 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer,
mSliceLiveDataMap = new ArrayMap<>(); mSliceLiveDataMap = new ArrayMap<>();
mControllerRendererPool = controllerRendererPool; mControllerRendererPool = controllerRendererPool;
mCardSet = new ArraySet<>(); mCardSet = new ArraySet<>();
mFlippedCardSet = new ArraySet<>();
mLifecycleOwner.getLifecycle().addObserver(this);
} }
@Override @Override
@@ -122,20 +129,23 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer,
} }
private void initDismissalActions(SliceViewHolder cardHolder, ContextualCard card) { private void initDismissalActions(SliceViewHolder cardHolder, ContextualCard card) {
final ViewFlipper viewFlipper = cardHolder.itemView.findViewById(R.id.viewFlipper);
cardHolder.sliceView.setOnLongClickListener(v -> { cardHolder.sliceView.setOnLongClickListener(v -> {
viewFlipper.showNext(); cardHolder.viewFlipper.showNext();
mFlippedCardSet.add(cardHolder);
return true; return true;
}); });
final Button btnKeep = cardHolder.itemView.findViewById(R.id.keep); final Button btnKeep = cardHolder.itemView.findViewById(R.id.keep);
btnKeep.setOnClickListener(v -> { btnKeep.setOnClickListener(v -> {
viewFlipper.showPrevious(); cardHolder.resetCard();
mFlippedCardSet.remove(cardHolder);
}); });
final Button btnRemove = cardHolder.itemView.findViewById(R.id.remove); final Button btnRemove = cardHolder.itemView.findViewById(R.id.remove);
btnRemove.setOnClickListener(v -> { btnRemove.setOnClickListener(v -> {
mControllerRendererPool.getController(mContext, card.getCardType()).onDismissed(card); mControllerRendererPool.getController(mContext, card.getCardType()).onDismissed(card);
cardHolder.resetCard();
mFlippedCardSet.remove(cardHolder);
}); });
} }
@@ -158,12 +168,24 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer,
} }
} }
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onStop() {
mFlippedCardSet.stream().forEach(holder -> holder.resetCard());
mFlippedCardSet.clear();
}
public static class SliceViewHolder extends RecyclerView.ViewHolder { public static class SliceViewHolder extends RecyclerView.ViewHolder {
public final SliceView sliceView; public final SliceView sliceView;
public final ViewFlipper viewFlipper;
public SliceViewHolder(View view) { public SliceViewHolder(View view) {
super(view); super(view);
sliceView = view.findViewById(R.id.slice_view); sliceView = view.findViewById(R.id.slice_view);
viewFlipper = view.findViewById(R.id.viewFlipper);
}
public void resetCard() {
viewFlipper.setDisplayedChild(0);
} }
} }
} }

View File

@@ -18,6 +18,7 @@ package com.android.settings.homepage.contextualcards.slices;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import android.app.Activity; import android.app.Activity;
@@ -51,10 +52,14 @@ import org.robolectric.android.controller.ActivityController;
@RunWith(SettingsRobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
public class SliceContextualCardRendererTest { public class SliceContextualCardRendererTest {
private static final String TEST_SLICE_URI = "content://test/test";
@Mock @Mock
private LiveData<Slice> mSliceLiveData; private LiveData<Slice> mSliceLiveData;
@Mock @Mock
private ControllerRendererPool mControllerRendererPool; private ControllerRendererPool mControllerRendererPool;
@Mock
private SliceContextualCardController mController;
private Activity mActivity; private Activity mActivity;
private SliceContextualCardRenderer mRenderer; private SliceContextualCardRenderer mRenderer;
@@ -75,10 +80,9 @@ public class SliceContextualCardRendererTest {
@Test @Test
public void bindView_shouldSetScrollableToFalse() { public void bindView_shouldSetScrollableToFalse() {
final String sliceUri = "content://com.android.settings.slices/action/flashlight";
RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
mRenderer.bindView(viewHolder, buildContextualCard(sliceUri)); mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI));
assertThat( assertThat(
((SliceContextualCardRenderer.SliceViewHolder) viewHolder).sliceView.isScrollable ((SliceContextualCardRenderer.SliceViewHolder) viewHolder).sliceView.isScrollable
@@ -99,64 +103,107 @@ public class SliceContextualCardRendererTest {
@Test @Test
public void bindView_newSliceLiveData_shouldAddDataToMap() { public void bindView_newSliceLiveData_shouldAddDataToMap() {
final String sliceUri = "content://com.android.settings.slices/action/flashlight"; mRenderer.bindView(getSliceViewHolder(), buildContextualCard(TEST_SLICE_URI));
mRenderer.bindView(getSliceViewHolder(), buildContextualCard(sliceUri));
assertThat(mRenderer.mSliceLiveDataMap.size()).isEqualTo(1); assertThat(mRenderer.mSliceLiveDataMap.size()).isEqualTo(1);
} }
@Test @Test
public void bindView_sliceLiveDataShouldObserveSliceView() { public void bindView_sliceLiveDataShouldObserveSliceView() {
final String sliceUri = "content://com.android.settings.slices/action/flashlight"; mRenderer.bindView(getSliceViewHolder(), buildContextualCard(TEST_SLICE_URI));
mRenderer.bindView(getSliceViewHolder(), buildContextualCard(sliceUri)); assertThat(mRenderer.mSliceLiveDataMap.get(TEST_SLICE_URI).hasObservers()).isTrue();
assertThat(mRenderer.mSliceLiveDataMap.get(sliceUri).hasObservers()).isTrue();
} }
@Test @Test
public void bindView_sliceLiveDataShouldRemoveObservers() { public void bindView_sliceLiveDataShouldRemoveObservers() {
final String sliceUri = "content://com.android.settings.slices/action/flashlight"; mRenderer.mSliceLiveDataMap.put(TEST_SLICE_URI, mSliceLiveData);
mRenderer.mSliceLiveDataMap.put(sliceUri, mSliceLiveData);
mRenderer.bindView(getSliceViewHolder(), buildContextualCard(sliceUri)); mRenderer.bindView(getSliceViewHolder(), buildContextualCard(TEST_SLICE_URI));
verify(mSliceLiveData).removeObservers(mLifecycleOwner); verify(mSliceLiveData).removeObservers(mLifecycleOwner);
} }
@Test @Test
public void longClick_shouldFlipCard() { public void longClick_shouldFlipCard() {
final String sliceUri = "content://com.android.settings.slices/action/flashlight";
final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
final View card = viewHolder.itemView.findViewById(R.id.slice_view); final View card = viewHolder.itemView.findViewById(R.id.slice_view);
final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.viewFlipper); final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.viewFlipper);
final View dismissalView = viewHolder.itemView.findViewById(R.id.dismissal_view); final View dismissalView = viewHolder.itemView.findViewById(R.id.dismissal_view);
mRenderer.bindView(viewHolder, buildContextualCard(sliceUri)); mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI));
assertThat(card).isNotNull();
card.performLongClick(); card.performLongClick();
assertThat(viewFlipper.getCurrentView()).isEqualTo(dismissalView); assertThat(viewFlipper.getCurrentView()).isEqualTo(dismissalView);
} }
@Test
public void longClick_shouldAddViewHolderToSet() {
final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
final View card = viewHolder.itemView.findViewById(R.id.slice_view);
mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI));
card.performLongClick();
assertThat(mRenderer.mFlippedCardSet).contains(viewHolder);
}
@Test @Test
public void viewClick_keepCard_shouldFlipBackToSlice() { public void viewClick_keepCard_shouldFlipBackToSlice() {
final String sliceUri = "content://com.android.settings.slices/action/flashlight";
final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
final View card = viewHolder.itemView.findViewById(R.id.slice_view); final View card = viewHolder.itemView.findViewById(R.id.slice_view);
final Button btnKeep = viewHolder.itemView.findViewById(R.id.keep); final Button btnKeep = viewHolder.itemView.findViewById(R.id.keep);
final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.viewFlipper); final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.viewFlipper);
mRenderer.bindView(viewHolder, buildContextualCard(sliceUri)); mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI));
assertThat(card).isNotNull();
card.performLongClick(); card.performLongClick();
assertThat(btnKeep).isNotNull();
btnKeep.performClick(); btnKeep.performClick();
assertThat(viewFlipper.getCurrentView()).isInstanceOf(SliceView.class); assertThat(viewFlipper.getCurrentView()).isInstanceOf(SliceView.class);
} }
@Test
public void viewClick_keepCard_shouldRemoveViewHolderFromSet() {
final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
final View card = viewHolder.itemView.findViewById(R.id.slice_view);
final Button btnKeep = viewHolder.itemView.findViewById(R.id.keep);
mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI));
card.performLongClick();
btnKeep.performClick();
assertThat(mRenderer.mFlippedCardSet).doesNotContain(viewHolder);
}
@Test
public void viewClick_removeCard_shouldRemoveViewHolderFromSet() {
final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
final View card = viewHolder.itemView.findViewById(R.id.slice_view);
final Button btnRemove = viewHolder.itemView.findViewById(R.id.remove);
final ContextualCard contextualCard = buildContextualCard(TEST_SLICE_URI);
mRenderer.bindView(viewHolder, contextualCard);
doReturn(mController).when(mControllerRendererPool).getController(mActivity,
ContextualCard.CardType.SLICE);
card.performLongClick();
btnRemove.performClick();
assertThat(mRenderer.mFlippedCardSet).doesNotContain(viewHolder);
}
@Test
public void onStop_cardIsFlipped_shouldFlipBack() {
final RecyclerView.ViewHolder viewHolder = getSliceViewHolder();
final View card = viewHolder.itemView.findViewById(R.id.slice_view);
final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.viewFlipper);
mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI));
card.performLongClick();
mRenderer.onStop();
assertThat(viewFlipper.getCurrentView()).isInstanceOf(SliceView.class);
}
private RecyclerView.ViewHolder getSliceViewHolder() { private RecyclerView.ViewHolder getSliceViewHolder() {
final int viewType = mRenderer.getViewType(false /* isHalfWidth */); final int viewType = mRenderer.getViewType(false /* isHalfWidth */);
final RecyclerView recyclerView = new RecyclerView(mActivity); final RecyclerView recyclerView = new RecyclerView(mActivity);
@@ -169,6 +216,7 @@ public class SliceContextualCardRendererTest {
private ContextualCard buildContextualCard(String sliceUri) { private ContextualCard buildContextualCard(String sliceUri) {
return new ContextualCard.Builder() return new ContextualCard.Builder()
.setName("test_name") .setName("test_name")
.setCardType(ContextualCard.CardType.SLICE)
.setSliceUri(Uri.parse(sliceUri)) .setSliceUri(Uri.parse(sliceUri))
.build(); .build();
} }