Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7fec8fc1 authored by Schneider Victor-tulias's avatar Schneider Victor-tulias
Browse files

Polish the gesture navigation tutorial sandbox

- Updated in-memory tutorial steps to a array so we can better track the tutorial progress after changing the theme.
- Added a 1.5 second delay for the gesture video when the user first starts a step, and when the app resumes.
- Updated the tutorial step indicator color: previous color was invisible against the background.

Bug: 188972959
Test: manual
Change-Id: Ia1a67f7cd9e7ed9802dbc0d88d1e3b432acfa127
parent 6f3081af
Loading
Loading
Loading
Loading
+17 −17
Original line number Diff line number Diff line
@@ -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;
    }

+50 −18
Original line number Diff line number Diff line
@@ -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,8 +280,10 @@ abstract class TutorialController implements BackGestureAttemptCallback,
                    gestureAnimation.stop();
                }

                if (!useGestureAnimationDelay) {
                    onStartRunnable.run();
                }
            }

            @Override
            public void onAnimationEnd(Drawable drawable) {
@@ -284,8 +296,28 @@ abstract class TutorialController implements BackGestureAttemptCallback,
            }
        });

        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) {
+3 −2
Original line number Diff line number Diff line
@@ -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 {
+5 −5
Original line number Diff line number Diff line
@@ -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));
                }
            }
        }