Merge "Polish the gesture navigation tutorial sandbox" into sc-dev am: cfa0baeca6

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/14924450

Change-Id: I21c2b5eb3d8b4d4d4c2cfdf16411a55b8bc6be1c
This commit is contained in:
TreeHugger Robot
2021-06-13 01:45:59 +00:00
committed by Automerger Merge Worker
4 changed files with 75 additions and 42 deletions

View File

@@ -29,8 +29,6 @@ import androidx.fragment.app.FragmentActivity;
import com.android.launcher3.R;
import com.android.quickstep.interaction.TutorialController.TutorialType;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
/** Shows the gesture interactive sandbox in full screen mode. */
@@ -39,8 +37,9 @@ public class GestureSandboxActivity extends FragmentActivity {
private static final String LOG_TAG = "GestureSandboxActivity";
private static final String KEY_TUTORIAL_STEPS = "tutorial_steps";
private static final String KEY_CURRENT_STEP = "current_step";
private Deque<TutorialType> mTutorialSteps;
private TutorialType[] mTutorialSteps;
private TutorialType mCurrentTutorialStep;
private TutorialFragment mFragment;
@@ -55,9 +54,7 @@ public class GestureSandboxActivity extends FragmentActivity {
Bundle args = savedInstanceState == null ? getIntent().getExtras() : savedInstanceState;
mTutorialSteps = getTutorialSteps(args);
mCurrentStep = 1;
mNumSteps = mTutorialSteps.size();
mCurrentTutorialStep = mTutorialSteps.pop();
mCurrentTutorialStep = mTutorialSteps[mCurrentStep - 1];
mFragment = TutorialFragment.newInstance(mCurrentTutorialStep);
getSupportFragmentManager().beginTransaction()
.add(R.id.gesture_tutorial_fragment_container, mFragment)
@@ -88,12 +85,13 @@ public class GestureSandboxActivity extends FragmentActivity {
@Override
protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
savedInstanceState.putStringArray(KEY_TUTORIAL_STEPS, getTutorialStepNames());
savedInstanceState.putInt(KEY_CURRENT_STEP, mCurrentStep);
super.onSaveInstanceState(savedInstanceState);
}
/** Returns true iff there aren't anymore tutorial types to display to the user. */
public boolean isTutorialComplete() {
return mTutorialSteps.isEmpty();
return mCurrentStep >= mNumSteps;
}
public int getCurrentStep() {
@@ -121,7 +119,7 @@ public class GestureSandboxActivity extends FragmentActivity {
mFragment.closeTutorial();
return;
}
mCurrentTutorialStep = mTutorialSteps.pop();
mCurrentTutorialStep = mTutorialSteps[mCurrentStep];
mFragment = TutorialFragment.newInstance(mCurrentTutorialStep);
getSupportFragmentManager().beginTransaction()
.replace(R.id.gesture_tutorial_fragment_container, mFragment)
@@ -131,10 +129,9 @@ public class GestureSandboxActivity extends FragmentActivity {
}
private String[] getTutorialStepNames() {
String[] tutorialStepNames = new String[mTutorialSteps.size() + 1];
String[] tutorialStepNames = new String[mTutorialSteps.length];
int i = 1;
tutorialStepNames[0] = mCurrentTutorialStep.name();
int i = 0;
for (TutorialType tutorialStep : mTutorialSteps) {
tutorialStepNames[i++] = tutorialStep.name();
}
@@ -142,25 +139,28 @@ public class GestureSandboxActivity extends FragmentActivity {
return tutorialStepNames;
}
private Deque<TutorialType> getTutorialSteps(Bundle extras) {
Deque<TutorialType> defaultSteps = new ArrayDeque<>();
defaultSteps.push(TutorialType.RIGHT_EDGE_BACK_NAVIGATION);
private TutorialType[] getTutorialSteps(Bundle extras) {
TutorialType[] defaultSteps = new TutorialType[] {TutorialType.LEFT_EDGE_BACK_NAVIGATION};
if (extras == null || !extras.containsKey(KEY_TUTORIAL_STEPS)) {
return defaultSteps;
}
String[] tutorialStepNames = extras.getStringArray(KEY_TUTORIAL_STEPS);
int currentStep = extras.getInt(KEY_CURRENT_STEP, -1);
if (tutorialStepNames == null) {
return defaultSteps;
}
Deque<TutorialType> tutorialSteps = new ArrayDeque<>();
for (String tutorialStepName : tutorialStepNames) {
tutorialSteps.addLast(TutorialType.valueOf(tutorialStepName));
TutorialType[] tutorialSteps = new TutorialType[tutorialStepNames.length];
for (int i = 0; i < tutorialStepNames.length; i++) {
tutorialSteps[i] = TutorialType.valueOf(tutorialStepNames[i]);
}
mCurrentStep = Math.max(currentStep, 1);
mNumSteps = tutorialSteps.length;
return tutorialSteps;
}

View File

@@ -58,6 +58,7 @@ abstract class TutorialController implements BackGestureAttemptCallback,
private static final int FEEDBACK_ANIMATION_MS = 250;
private static final int RIPPLE_VISIBLE_MS = 300;
private static final int GESTURE_ANIMATION_DELAY_MS = 1500;
final TutorialFragment mTutorialFragment;
TutorialType mTutorialType;
@@ -65,6 +66,7 @@ abstract class TutorialController implements BackGestureAttemptCallback,
final TextView mCloseButton;
final ViewGroup mFeedbackView;
final TextView mFeedbackTitleView;
final ImageView mFeedbackVideoView;
final ImageView mGestureVideoView;
final RelativeLayout mFakeLauncherView;
@@ -80,6 +82,12 @@ abstract class TutorialController implements BackGestureAttemptCallback,
protected boolean mGestureCompleted = false;
// These runnables should be used when posting callbacks to their views and cleared from their
// views before posting new callbacks.
private final Runnable mTitleViewCallback;
@Nullable private Runnable mFeedbackViewCallback;
@Nullable private Runnable mFeedbackVideoViewCallback;
TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
mTutorialFragment = tutorialFragment;
mTutorialType = tutorialType;
@@ -89,6 +97,8 @@ abstract class TutorialController implements BackGestureAttemptCallback,
mCloseButton = rootView.findViewById(R.id.gesture_tutorial_fragment_close_button);
mCloseButton.setOnClickListener(button -> showSkipTutorialDialog());
mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
mFeedbackTitleView = mFeedbackView.findViewById(
R.id.gesture_tutorial_fragment_feedback_title);
mFeedbackVideoView = rootView.findViewById(R.id.gesture_tutorial_feedback_video);
mGestureVideoView = rootView.findViewById(R.id.gesture_tutorial_gesture_video);
mFakeLauncherView = rootView.findViewById(R.id.gesture_tutorial_fake_launcher_view);
@@ -103,6 +113,9 @@ abstract class TutorialController implements BackGestureAttemptCallback,
mTutorialStepView =
rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_tutorial_step);
mSkipTutorialDialog = createSkipTutorialDialog();
mTitleViewCallback = () -> mFeedbackTitleView.sendAccessibilityEvent(
AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
private void showSkipTutorialDialog() {
@@ -169,7 +182,7 @@ abstract class TutorialController implements BackGestureAttemptCallback,
playFeedbackVideo(tutorialAnimation, gestureAnimation, () -> {
mFeedbackView.setTranslationY(0);
title.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
});
}, true);
}
}
@@ -192,15 +205,17 @@ abstract class TutorialController implements BackGestureAttemptCallback,
isGestureSuccessful
? R.string.gesture_tutorial_nice : R.string.gesture_tutorial_try_again,
subtitleResId,
isGestureSuccessful);
isGestureSuccessful,
false);
}
void showFeedback(
int titleResId,
int subtitleResId,
boolean isGestureSuccessful) {
TextView title = mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_title);
title.setText(titleResId);
boolean isGestureSuccessful,
boolean useGestureAnimationDelay) {
mFeedbackTitleView.setText(titleResId);
mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
TextView subtitle =
mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_subtitle);
subtitle.setText(subtitleResId);
@@ -221,11 +236,8 @@ abstract class TutorialController implements BackGestureAttemptCallback,
.setDuration(FEEDBACK_ANIMATION_MS)
.translationY(0)
.start();
title.postDelayed(
() -> title.sendAccessibilityEvent(
AccessibilityEvent.TYPE_VIEW_FOCUSED),
FEEDBACK_ANIMATION_MS);
});
mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS);
}, useGestureAnimationDelay);
return;
} else {
mTutorialFragment.releaseFeedbackVideoView();
@@ -237,10 +249,7 @@ abstract class TutorialController implements BackGestureAttemptCallback,
.setDuration(FEEDBACK_ANIMATION_MS)
.translationY(0)
.start();
title.postDelayed(
() -> title.sendAccessibilityEvent(
AccessibilityEvent.TYPE_VIEW_FOCUSED),
FEEDBACK_ANIMATION_MS);
mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS);
}
void hideFeedback(boolean releaseFeedbackVideo) {
@@ -254,7 +263,8 @@ abstract class TutorialController implements BackGestureAttemptCallback,
private void playFeedbackVideo(
@NonNull AnimatedVectorDrawable tutorialAnimation,
@NonNull AnimatedVectorDrawable gestureAnimation,
@NonNull Runnable onStartRunnable) {
@NonNull Runnable onStartRunnable,
boolean useGestureAnimationDelay) {
if (tutorialAnimation.isRunning()) {
tutorialAnimation.reset();
@@ -270,7 +280,9 @@ abstract class TutorialController implements BackGestureAttemptCallback,
gestureAnimation.stop();
}
onStartRunnable.run();
if (!useGestureAnimationDelay) {
onStartRunnable.run();
}
}
@Override
@@ -284,8 +296,28 @@ abstract class TutorialController implements BackGestureAttemptCallback,
}
});
tutorialAnimation.start();
mFeedbackVideoView.setVisibility(View.VISIBLE);
if (mFeedbackViewCallback != null) {
mFeedbackVideoView.removeCallbacks(mFeedbackViewCallback);
mFeedbackViewCallback = null;
}
if (mFeedbackVideoViewCallback != null) {
mFeedbackVideoView.removeCallbacks(mFeedbackVideoViewCallback);
mFeedbackVideoViewCallback = null;
}
if (useGestureAnimationDelay) {
mFeedbackViewCallback = onStartRunnable;
mFeedbackVideoViewCallback = () -> {
mFeedbackVideoView.setVisibility(View.VISIBLE);
tutorialAnimation.start();
};
mFeedbackVideoView.setVisibility(View.GONE);
mFeedbackView.post(mFeedbackViewCallback);
mFeedbackVideoView.postDelayed(mFeedbackVideoViewCallback, GESTURE_ANIMATION_DELAY_MS);
} else {
mFeedbackVideoView.setVisibility(View.VISIBLE);
tutorialAnimation.start();
}
}
void setRippleHotspot(float x, float y) {

View File

@@ -170,7 +170,8 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
Integer introTileStringResId = mTutorialController.getIntroductionTitle();
Integer introSubtitleResId = mTutorialController.getIntroductionSubtitle();
if (introTileStringResId != null && introSubtitleResId != null) {
mTutorialController.showFeedback(introTileStringResId, introSubtitleResId, false);
mTutorialController.showFeedback(
introTileStringResId, introSubtitleResId, false, true);
mIntroductionShown = true;
}
}
@@ -252,7 +253,7 @@ abstract class TutorialFragment extends Fragment implements OnTouchListener {
@Override
public void onResume() {
super.onResume();
if (mFragmentStopped) {
if (mFragmentStopped && mTutorialController != null) {
mTutorialController.showFeedback();
mFragmentStopped = false;
} else {

View File

@@ -23,6 +23,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -86,6 +87,8 @@ public class TutorialStepIndicator extends LinearLayout {
for (int i = mTotalSteps; i < getChildCount(); i++) {
removeViewAt(i);
}
int stepIndicatorColor = GraphicsUtils.getAttrColor(
getContext(), android.R.attr.textColorPrimary);
for (int i = 0; i < mTotalSteps; i++) {
Drawable pageIndicatorPillDrawable = AppCompatResources.getDrawable(
getContext(), R.drawable.tutorial_step_indicator_pill);
@@ -104,13 +107,10 @@ public class TutorialStepIndicator extends LinearLayout {
}
if (pageIndicatorPillDrawable != null) {
if (i < mCurrentStep) {
pageIndicatorPillDrawable.setTint(
GraphicsUtils.getAttrColor(getContext(),
android.R.attr.textColorPrimary));
pageIndicatorPillDrawable.setTint(stepIndicatorColor);
} else {
pageIndicatorPillDrawable.setTint(
GraphicsUtils.getAttrColor(getContext(),
android.R.attr.textColorPrimaryInverse));
ColorUtils.setAlphaComponent(stepIndicatorColor, 0x22));
}
}
}