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

Commit 31f787d3 authored by Tony Wickham's avatar Tony Wickham
Browse files

Add "wave" animation when entering taskbar edu

Each icon scales and translates up, then back down. If the icon is predicted, it also plays a "slot machine" animation through random icons from AllAppsList.

Test: Visual
Bug: 180605356
Change-Id: Ia41cb0e340347eea6b580d23c8a2386837e9c399
parent 8e0484bf
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -166,5 +166,6 @@
    <dimen name="taskbar_stashed_size">24dp</dimen>
    <dimen name="taskbar_stashed_handle_width">220dp</dimen>
    <dimen name="taskbar_stashed_handle_height">6dp</dimen>
    <dimen name="taskbar_edu_bg_corner_radius">28dp</dimen>
    <dimen name="taskbar_edu_wave_anim_trans_y">25dp</dimen>
    <dimen name="taskbar_edu_wave_anim_trans_y_return_overshoot">4dp</dimen>
</resources>
+9 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.OnboardingPrefs;
@@ -45,6 +46,9 @@ import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;

import java.util.Arrays;
import java.util.stream.Stream;

/**
 * A data source which integrates with a Launcher instance
 */
@@ -268,6 +272,11 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
        mTaskbarOverrideBackgroundAlpha.updateValue(forceHide ? 0 : 1);
    }

    @Override
    public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
        return Arrays.stream(mLauncher.getAppsView().getAppsStore().getApps());
    }

    /**
     * Starts the taskbar education flow, if the user hasn't seen it yet.
     */
+1 −0
Original line number Diff line number Diff line
@@ -78,6 +78,7 @@ public class TaskbarControllers {
        taskbarKeyguardController.init(navbarButtonsViewController);
        stashedHandleViewController.init(this);
        taskbarStashController.init(this);
        taskbarEduController.init(this);
    }

    /**
+141 −0
Original line number Diff line number Diff line
@@ -15,16 +15,72 @@
 */
package com.android.launcher3.taskbar;

import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.Keyframe;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.content.res.Resources;
import android.text.TextUtils;
import android.view.View;

import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.uioverrides.PredictedAppIcon;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/** Handles the Taskbar Education flow. */
public class TaskbarEduController {

    private static final long WAVE_ANIM_DELAY = 250;
    private static final long WAVE_ANIM_STAGGER = 50;
    private static final long WAVE_ANIM_EACH_ICON_DURATION = 633;
    private static final long WAVE_ANIM_SLOT_MACHINE_DURATION = 1085;
    // The fraction of each icon's animation at which we reach the top point of the wave.
    private static final float WAVE_ANIM_FRACTION_TOP = 0.4f;
    // The fraction of each icon's animation at which we reach the bottom, before overshooting.
    private static final float WAVE_ANIM_FRACTION_BOTTOM = 0.9f;
    private static final TimeInterpolator WAVE_ANIM_TO_TOP_INTERPOLATOR = FAST_OUT_SLOW_IN;
    private static final TimeInterpolator WAVE_ANIM_TO_BOTTOM_INTERPOLATOR = ACCEL_2;
    private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_INTERPOLATOR = DEACCEL;
    private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR = ACCEL_DEACCEL;
    private static final float WAVE_ANIM_ICON_SCALE = 1.2f;
    // How many icons to cycle through in the slot machine (+ the original icon at each end).
    private static final int WAVE_ANIM_SLOT_MACHINE_NUM_ICONS = 3;

    private final TaskbarActivityContext mActivity;
    private final float mWaveAnimTranslationY;
    private final float mWaveAnimTranslationYReturnOvershoot;

    // Initialized in init.
    TaskbarControllers mControllers;

    private TaskbarEduView mTaskbarEduView;
    private Animator mAnim;

    public TaskbarEduController(TaskbarActivityContext activity) {
        mActivity = activity;

        final Resources resources = activity.getResources();
        mWaveAnimTranslationY = resources.getDimension(R.dimen.taskbar_edu_wave_anim_trans_y);
        mWaveAnimTranslationYReturnOvershoot = resources.getDimension(
                R.dimen.taskbar_edu_wave_anim_trans_y_return_overshoot);
    }

    public void init(TaskbarControllers controllers) {
        mControllers = controllers;
    }

    void showEdu() {
@@ -35,6 +91,7 @@ public class TaskbarEduController {
            mTaskbarEduView.init(new TaskbarEduCallbacks());
            mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null);
            mTaskbarEduView.show();
            startAnim(createWaveAnim());
        });
    }

@@ -44,6 +101,90 @@ public class TaskbarEduController {
        }
    }

    /**
     * Starts the given animation, ending the previous animation first if it's still playing.
     */
    private void startAnim(Animator anim) {
        if (mAnim != null) {
            mAnim.end();
        }
        mAnim = anim;
        mAnim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mAnim = null;
            }
        });
        mAnim.start();
    }

    /**
     * Creates a staggered "wave" animation where each icon translates and scales up in succession.
     */
    private Animator createWaveAnim() {
        AnimatorSet waveAnim = new AnimatorSet();
        View[] icons = mControllers.taskbarViewController.getIconViews();
        for (int i = 0; i < icons.length; i++) {
            View icon = icons[i];
            AnimatorSet iconAnim = new AnimatorSet();

            Keyframe[] scaleKeyframes = new Keyframe[] {
                    Keyframe.ofFloat(0, 1f),
                    Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, WAVE_ANIM_ICON_SCALE),
                    Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 1f),
                    Keyframe.ofFloat(1f, 1f)
            };
            scaleKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
            scaleKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);

            Keyframe[] translationYKeyframes = new Keyframe[] {
                    Keyframe.ofFloat(0, 0f),
                    Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, -mWaveAnimTranslationY),
                    Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 0f),
                    // Half of the remaining fraction overshoots, then the other half returns to 0.
                    Keyframe.ofFloat(
                            WAVE_ANIM_FRACTION_BOTTOM + (1 - WAVE_ANIM_FRACTION_BOTTOM) / 2f,
                            mWaveAnimTranslationYReturnOvershoot),
                    Keyframe.ofFloat(1f, 0f)
            };
            translationYKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
            translationYKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
            translationYKeyframes[3].setInterpolator(WAVE_ANIM_OVERSHOOT_INTERPOLATOR);
            translationYKeyframes[4].setInterpolator(WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR);

            iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
                    PropertyValuesHolder.ofKeyframe(SCALE_PROPERTY, scaleKeyframes))
                    .setDuration(WAVE_ANIM_EACH_ICON_DURATION));
            iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
                    PropertyValuesHolder.ofKeyframe(View.TRANSLATION_Y, translationYKeyframes))
                    .setDuration(WAVE_ANIM_EACH_ICON_DURATION));

            if (icon instanceof PredictedAppIcon) {
                // Play slot machine animation through random icons from AllAppsList.
                PredictedAppIcon predictedAppIcon = (PredictedAppIcon) icon;
                ItemInfo itemInfo = (ItemInfo) icon.getTag();
                List<BitmapInfo> iconsToAnimate = mControllers.uiController.getAppIconsForEdu()
                        .filter(appInfo -> !TextUtils.equals(appInfo.title, itemInfo.title))
                        .map(appInfo -> appInfo.bitmap)
                        .filter(bitmap -> !bitmap.isNullOrLowRes())
                        .collect(Collectors.toList());
                // Pick n icons at random.
                Collections.shuffle(iconsToAnimate);
                if (iconsToAnimate.size() > WAVE_ANIM_SLOT_MACHINE_NUM_ICONS) {
                    iconsToAnimate = iconsToAnimate.subList(0, WAVE_ANIM_SLOT_MACHINE_NUM_ICONS);
                }
                Animator slotMachineAnim = predictedAppIcon.createSlotMachineAnim(iconsToAnimate);
                if (slotMachineAnim != null) {
                    iconAnim.play(slotMachineAnim.setDuration(WAVE_ANIM_SLOT_MACHINE_DURATION));
                }
            }

            iconAnim.setStartDelay(WAVE_ANIM_STAGGER * i);
            waveAnim.play(iconAnim);
        }
        waveAnim.setStartDelay(WAVE_ANIM_DELAY);
        return waveAnim;
    }

    /**
     * Callbacks for {@link TaskbarEduView} to interact with its controller.
+4 −3
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@
 */
package com.android.launcher3.taskbar;

import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;

import android.animation.PropertyValuesHolder;
import android.content.Context;
@@ -33,6 +33,7 @@ import com.android.launcher3.views.AbstractSlideInView;
public class TaskbarEduView extends AbstractSlideInView<TaskbarActivityContext>
        implements Insettable {

    private static final int DEFAULT_OPEN_DURATION = 500;
    private static final int DEFAULT_CLOSE_DURATION = 200;

    private final Rect mInsets = new Rect();
@@ -129,8 +130,8 @@ public class TaskbarEduView extends AbstractSlideInView<TaskbarActivityContext>
        mIsOpen = true;
        mOpenCloseAnimator.setValues(
                PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
        mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
        mOpenCloseAnimator.start();
        mOpenCloseAnimator.setInterpolator(AGGRESSIVE_EASE);
        mOpenCloseAnimator.setDuration(DEFAULT_OPEN_DURATION).start();
    }

    void snapToPage(int page) {
Loading