From f3bbd98bf8958f593c08c70539ba9815b97ce908 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Fri, 13 Aug 2021 13:53:14 -0700 Subject: [PATCH] Allow users to dismiss notifications in popup view. Bug: 193014051 Test: - dismiss notifications (left/right) - open popup, dismiss notification from shade and ensure popup gets updates - open popup, trigger new notification and ensure popup gets updated Change-Id: Iea4d458218cbf5cb22f5f89aa0a4cc1bee18cc73 --- res/layout/notification_content.xml | 12 +- res/layout/popup_container.xml | 7 +- res/values/dimens.xml | 2 + .../notification/NotificationContainer.java | 283 ++++++++++++++++++ .../notification/NotificationItemView.java | 179 ----------- .../notification/NotificationMainView.java | 231 +++++++++++--- .../android/launcher3/popup/ArrowPopup.java | 7 + .../popup/PopupContainerWithArrow.java | 57 ++-- 8 files changed, 513 insertions(+), 265 deletions(-) create mode 100644 src/com/android/launcher3/notification/NotificationContainer.java delete mode 100644 src/com/android/launcher3/notification/NotificationItemView.java diff --git a/res/layout/notification_content.xml b/res/layout/notification_content.xml index 84822a671b..91897e9e75 100644 --- a/res/layout/notification_content.xml +++ b/res/layout/notification_content.xml @@ -14,10 +14,11 @@ limitations under the License. --> - + android:layout_height="wrap_content" + android:orientation="vertical"> - - - \ No newline at end of file + + \ No newline at end of file diff --git a/res/layout/popup_container.xml b/res/layout/popup_container.xml index 18014bb1d0..9327287018 100644 --- a/res/layout/popup_container.xml +++ b/res/layout/popup_container.xml @@ -31,12 +31,9 @@ android:elevation="@dimen/deep_shortcuts_elevation" android:orientation="vertical"/> - + android:visibility="gone"/> \ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml index f434644996..7d8012fb12 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -271,6 +271,8 @@ 8dp + 8dp + 8dp 16dp 18dp 14sp diff --git a/src/com/android/launcher3/notification/NotificationContainer.java b/src/com/android/launcher3/notification/NotificationContainer.java new file mode 100644 index 0000000000..9eb05cd40f --- /dev/null +++ b/src/com/android/launcher3/notification/NotificationContainer.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.notification; + +import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity; +import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.FloatProperty; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; + +import com.android.launcher3.R; +import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.popup.PopupContainerWithArrow; +import com.android.launcher3.touch.BaseSwipeDetector; +import com.android.launcher3.touch.OverScroll; +import com.android.launcher3.touch.SingleAxisSwipeDetector; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Class to manage the notification UI in a {@link PopupContainerWithArrow}. + * + * - Has two {@link NotificationMainView} that represent the top two notifications + * - Handles dismissing a notification + */ +public class NotificationContainer extends FrameLayout implements SingleAxisSwipeDetector.Listener { + + private static final FloatProperty DRAG_TRANSLATION_X = + new FloatProperty("notificationProgress") { + @Override + public void setValue(NotificationContainer view, float transX) { + view.setDragTranslationX(transX); + } + + @Override + public Float get(NotificationContainer view) { + return view.mDragTranslationX; + } + }; + + private static final Rect sTempRect = new Rect(); + + private final SingleAxisSwipeDetector mSwipeDetector; + private final List mNotificationInfos = new ArrayList<>(); + private boolean mIgnoreTouch = false; + + private final ObjectAnimator mContentTranslateAnimator; + private float mDragTranslationX = 0; + + private final NotificationMainView mPrimaryView; + private final NotificationMainView mSecondaryView; + private PopupContainerWithArrow mPopupContainer; + + public NotificationContainer(Context context) { + this(context, null, 0); + } + + public NotificationContainer(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NotificationContainer(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mSwipeDetector = new SingleAxisSwipeDetector(getContext(), this, HORIZONTAL); + mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_BOTH, false); + mContentTranslateAnimator = ObjectAnimator.ofFloat(this, DRAG_TRANSLATION_X, 0); + + mPrimaryView = (NotificationMainView) View.inflate(getContext(), + R.layout.notification_content, null); + mSecondaryView = (NotificationMainView) View.inflate(getContext(), + R.layout.notification_content, null); + mSecondaryView.setAlpha(0); + + addView(mSecondaryView); + addView(mPrimaryView); + + } + + public void setPopupView(PopupContainerWithArrow popupView) { + mPopupContainer = popupView; + } + + /** + * Returns true if we should intercept the swipe. + */ + public boolean onInterceptSwipeEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + sTempRect.set(getLeft(), getTop(), getRight(), getBottom()); + mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY()); + if (!mIgnoreTouch) { + mPopupContainer.getParent().requestDisallowInterceptTouchEvent(true); + } + } + if (mIgnoreTouch) { + return false; + } + if (mPrimaryView.getNotificationInfo() == null) { + // The notification hasn't been populated yet. + return false; + } + + mSwipeDetector.onTouchEvent(ev); + return mSwipeDetector.isDraggingOrSettling(); + } + + /** + * Returns true when we should handle the swipe. + */ + public boolean onSwipeEvent(MotionEvent ev) { + if (mIgnoreTouch) { + return false; + } + if (mPrimaryView.getNotificationInfo() == null) { + // The notification hasn't been populated yet. + return false; + } + return mSwipeDetector.onTouchEvent(ev); + } + + /** + * Applies the list of @param notificationInfos to this container. + */ + public void applyNotificationInfos(final List notificationInfos) { + mNotificationInfos.clear(); + if (notificationInfos.isEmpty()) { + mPrimaryView.applyNotificationInfo(null); + mSecondaryView.applyNotificationInfo(null); + return; + } + mNotificationInfos.addAll(notificationInfos); + + NotificationInfo mainNotification = notificationInfos.get(0); + mPrimaryView.applyNotificationInfo(mainNotification); + mSecondaryView.applyNotificationInfo(notificationInfos.size() > 1 + ? notificationInfos.get(1) + : null); + } + + /** + * Trims the notifications. + * @param notificationKeys List of all valid notification keys. + */ + public void trimNotifications(final List notificationKeys) { + Iterator iterator = mNotificationInfos.iterator(); + while (iterator.hasNext()) { + if (!notificationKeys.contains(iterator.next().notificationKey)) { + iterator.remove(); + } + } + + NotificationInfo primaryInfo = mNotificationInfos.size() > 0 + ? mNotificationInfos.get(0) + : null; + NotificationInfo secondaryInfo = mNotificationInfos.size() > 1 + ? mNotificationInfos.get(1) + : null; + + mPrimaryView.applyNotificationInfo(primaryInfo); + mSecondaryView.applyNotificationInfo(secondaryInfo); + + mPrimaryView.onPrimaryDrag(0); + mSecondaryView.onSecondaryDrag(0); + } + + private void setDragTranslationX(float translationX) { + mDragTranslationX = translationX; + + float progress = translationX / getWidth(); + mPrimaryView.onPrimaryDrag(progress); + if (mSecondaryView.getNotificationInfo() == null) { + mSecondaryView.setAlpha(0f); + } else { + mSecondaryView.onSecondaryDrag(progress); + } + } + + // SingleAxisSwipeDetector.Listener's + @Override + public void onDragStart(boolean start, float startDisplacement) { + mPopupContainer.showArrow(false); + } + + @Override + public boolean onDrag(float displacement) { + if (!mPrimaryView.canChildBeDismissed()) { + displacement = OverScroll.dampedScroll(displacement, getWidth()); + } + + float progress = displacement / getWidth(); + mPrimaryView.onPrimaryDrag(progress); + if (mSecondaryView.getNotificationInfo() == null) { + mSecondaryView.setAlpha(0f); + } else { + mSecondaryView.onSecondaryDrag(progress); + } + mContentTranslateAnimator.cancel(); + return true; + } + + @Override + public void onDragEnd(float velocity) { + final boolean willExit; + final float endTranslation; + final float startTranslation = mPrimaryView.getTranslationX(); + final float width = getWidth(); + + if (!mPrimaryView.canChildBeDismissed()) { + willExit = false; + endTranslation = 0; + } else if (mSwipeDetector.isFling(velocity)) { + willExit = true; + endTranslation = velocity < 0 ? -width : width; + } else if (Math.abs(startTranslation) > width / 2f) { + willExit = true; + endTranslation = (startTranslation < 0 ? -width : width); + } else { + willExit = false; + endTranslation = 0; + } + + long duration = BaseSwipeDetector.calculateDuration(velocity, + (endTranslation - startTranslation) / width); + + mContentTranslateAnimator.removeAllListeners(); + mContentTranslateAnimator.setDuration(duration) + .setInterpolator(scrollInterpolatorForVelocity(velocity)); + mContentTranslateAnimator.setFloatValues(startTranslation, endTranslation); + + NotificationMainView current = mPrimaryView; + mContentTranslateAnimator.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + mSwipeDetector.finishedScrolling(); + if (willExit) { + current.onChildDismissed(); + } + mPopupContainer.showArrow(true); + } + }); + mContentTranslateAnimator.start(); + } + + /** + * Animates the background color to a new color. + * @param color The color to change to. + * @param animatorSetOut The AnimatorSet where we add the color animator to. + */ + public void updateBackgroundColor(int color, AnimatorSet animatorSetOut) { + mPrimaryView.updateBackgroundColor(color, animatorSetOut); + mSecondaryView.updateBackgroundColor(color, animatorSetOut); + } + + /** + * Updates the header with a new @param notificationCount. + */ + public void updateHeader(int notificationCount) { + mPrimaryView.updateHeader(notificationCount); + mSecondaryView.updateHeader(notificationCount - 1); + } +} diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java deleted file mode 100644 index af943a6933..0000000000 --- a/src/com/android/launcher3/notification/NotificationItemView.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.notification; - -import android.animation.AnimatorSet; -import android.content.Context; -import android.graphics.Outline; -import android.graphics.Rect; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroup.MarginLayoutParams; -import android.view.ViewOutlineProvider; -import android.widget.TextView; - -import com.android.launcher3.R; -import com.android.launcher3.popup.PopupContainerWithArrow; -import com.android.launcher3.util.Themes; - -import java.util.ArrayList; -import java.util.List; - -/** - * Utility class to manage notification UI - */ -public class NotificationItemView { - - private static final Rect sTempRect = new Rect(); - - private final Context mContext; - private final PopupContainerWithArrow mPopupContainer; - private final ViewGroup mRootView; - - private final TextView mHeaderCount; - private final NotificationMainView mMainView; - - private final View mHeader; - - private View mGutter; - - private boolean mIgnoreTouch = false; - private List mNotificationInfos = new ArrayList<>(); - - public NotificationItemView(PopupContainerWithArrow container, ViewGroup rootView) { - mPopupContainer = container; - mRootView = rootView; - mContext = container.getContext(); - - mHeaderCount = container.findViewById(R.id.notification_count); - mMainView = container.findViewById(R.id.main_view); - - mHeader = container.findViewById(R.id.header); - - float radius = Themes.getDialogCornerRadius(mContext); - rootView.setClipToOutline(true); - rootView.setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), radius); - } - }); - } - - /** - * Animates the background color to a new color. - * @param color The color to change to. - * @param animatorSetOut The AnimatorSet where we add the color animator to. - */ - public void updateBackgroundColor(int color, AnimatorSet animatorSetOut) { - mMainView.updateBackgroundColor(color, animatorSetOut); - } - - public void addGutter() { - if (mGutter == null) { - mGutter = mPopupContainer.inflateAndAdd(R.layout.notification_gutter, mRootView); - } - } - - public void inverseGutterMargin() { - MarginLayoutParams lp = (MarginLayoutParams) mGutter.getLayoutParams(); - int top = lp.topMargin; - lp.topMargin = lp.bottomMargin; - lp.bottomMargin = top; - } - - public void removeAllViews() { - mRootView.removeView(mMainView); - mRootView.removeView(mHeader); - if (mGutter != null) { - mRootView.removeView(mGutter); - } - } - - /** - * Updates the header text. - * @param notificationCount The number of notifications. - */ - public void updateHeader(int notificationCount) { - final String text; - final int visibility; - if (notificationCount <= 1) { - text = ""; - visibility = View.INVISIBLE; - } else { - text = String.valueOf(notificationCount); - visibility = View.VISIBLE; - - } - mHeaderCount.setText(text); - mHeaderCount.setVisibility(visibility); - } - - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - sTempRect.set(mRootView.getLeft(), mRootView.getTop(), - mRootView.getRight(), mRootView.getBottom()); - mIgnoreTouch = !sTempRect.contains((int) ev.getX(), (int) ev.getY()); - if (!mIgnoreTouch) { - mPopupContainer.getParent().requestDisallowInterceptTouchEvent(true); - } - } - if (mIgnoreTouch) { - return false; - } - if (mMainView.getNotificationInfo() == null) { - // The notification hasn't been populated yet. - return false; - } - - return false; - } - - public void applyNotificationInfos(final List notificationInfos) { - mNotificationInfos.clear(); - if (notificationInfos.isEmpty()) { - return; - } - mNotificationInfos.addAll(notificationInfos); - - NotificationInfo mainNotification = notificationInfos.get(0); - mMainView.applyNotificationInfo(mainNotification, false); - } - - public void trimNotifications(final List notificationKeys) { - NotificationInfo currentMainNotificationInfo = mMainView.getNotificationInfo(); - boolean shouldUpdateMainNotification = !notificationKeys.contains( - currentMainNotificationInfo.notificationKey); - - if (shouldUpdateMainNotification) { - int size = notificationKeys.size(); - NotificationInfo nextNotification = null; - // We get the latest notification by finding the notification after the one that was - // just dismissed. - for (int i = 0; i < size; ++i) { - if (currentMainNotificationInfo == mNotificationInfos.get(i) && i + 1 < size) { - nextNotification = mNotificationInfos.get(i + 1); - break; - } - } - if (nextNotification != null) { - mMainView.applyNotificationInfo(nextNotification, true); - } - } - } -} diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java index b8aa8246fb..f9ff8a6e56 100644 --- a/src/com/android/launcher3/notification/NotificationMainView.java +++ b/src/com/android/launcher3/notification/NotificationMainView.java @@ -16,62 +16,70 @@ package com.android.launcher3.notification; +import static com.android.launcher3.Utilities.mapToRange; +import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DISMISSED; import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.content.Context; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; +import android.graphics.Outline; +import android.graphics.Rect; +import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.FloatProperty; import android.view.View; import android.view.ViewGroup; -import android.widget.FrameLayout; +import android.view.ViewOutlineProvider; +import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.launcher3.Launcher; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.touch.SingleAxisSwipeDetector; +import com.android.launcher3.util.Themes; /** * A {@link android.widget.FrameLayout} that contains a single notification, * e.g. icon + title + text. */ @TargetApi(Build.VERSION_CODES.N) -public class NotificationMainView extends FrameLayout { - - private static final FloatProperty CONTENT_TRANSLATION = - new FloatProperty("contentTranslation") { - @Override - public void setValue(NotificationMainView view, float v) { - view.setContentTranslation(v); - } - - @Override - public Float get(NotificationMainView view) { - return view.mTextAndBackground.getTranslationX(); - } - }; +public class NotificationMainView extends LinearLayout { // This is used only to track the notification view, so that it can be properly logged. public static final ItemInfo NOTIFICATION_ITEM_INFO = new ItemInfo(); + // Value when the primary notification main view will be gone (zero alpha). + private static final float PRIMARY_GONE_PROGRESS = 0.7f; + private static final float PRIMARY_MIN_PROGRESS = 0.40f; + private static final float PRIMARY_MAX_PROGRESS = 0.60f; + private static final float SECONDARY_MIN_PROGRESS = 0.30f; + private static final float SECONDARY_MAX_PROGRESS = 0.50f; + private static final float SECONDARY_CONTENT_MAX_PROGRESS = 0.6f; + private NotificationInfo mNotificationInfo; - private ViewGroup mTextAndBackground; private int mBackgroundColor; private TextView mTitleView; private TextView mTextView; private View mIconView; - private SingleAxisSwipeDetector mSwipeDetector; + private View mHeader; + private View mMainView; - private final ColorDrawable mColorDrawable; + private TextView mHeaderCount; + private final Rect mOutline = new Rect(); + + // Space between notifications during swipe + private final int mNotificationSpace; + private final int mMaxTransX; + private final int mMaxElevation; + + private final GradientDrawable mBackground; public NotificationMainView(Context context) { this(context, null, 0); @@ -82,28 +90,77 @@ public class NotificationMainView extends FrameLayout { } public NotificationMainView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + this(context, attrs, defStyle, 0); + } - mColorDrawable = new ColorDrawable(Color.TRANSPARENT); + public NotificationMainView(Context context, AttributeSet attrs, int defStyle, int defStylRes) { + super(context, attrs, defStyle, defStylRes); + + float outlineRadius = Themes.getDialogCornerRadius(context); + + mBackground = new GradientDrawable(); + mBackground.setColor(Themes.getAttrColor(context, R.attr.popupColorPrimary)); + mBackground.setCornerRadius(outlineRadius); + setBackground(mBackground); + + mMaxElevation = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_elevation); + setElevation(mMaxElevation); + + mMaxTransX = getResources().getDimensionPixelSize(R.dimen.notification_max_trans); + mNotificationSpace = getResources().getDimensionPixelSize(R.dimen.notification_space); + + setClipToOutline(true); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(mOutline, outlineRadius); + } + }); + } + + /** + * Updates the header text. + * @param notificationCount The number of notifications. + */ + public void updateHeader(int notificationCount) { + final String text; + final int visibility; + if (notificationCount <= 1) { + text = ""; + visibility = View.INVISIBLE; + } else { + text = String.valueOf(notificationCount); + visibility = View.VISIBLE; + + } + mHeaderCount.setText(text); + mHeaderCount.setVisibility(visibility); } @Override protected void onFinishInflate() { super.onFinishInflate(); - mTextAndBackground = findViewById(R.id.text_and_background); - mTitleView = mTextAndBackground.findViewById(R.id.title); - mTextView = mTextAndBackground.findViewById(R.id.text); + ViewGroup textAndBackground = findViewById(R.id.text_and_background); + mTitleView = textAndBackground.findViewById(R.id.title); + mTextView = textAndBackground.findViewById(R.id.text); mIconView = findViewById(R.id.popup_item_icon); + mHeaderCount = findViewById(R.id.notification_count); - ColorDrawable colorBackground = (ColorDrawable) mTextAndBackground.getBackground(); - updateBackgroundColor(colorBackground.getColor()); + mHeader = findViewById(R.id.header); + mMainView = findViewById(R.id.main_view); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + mOutline.set(0, 0, getWidth(), getHeight()); + invalidateOutline(); } private void updateBackgroundColor(int color) { mBackgroundColor = color; - mColorDrawable.setColor(color); - mTextAndBackground.setBackground(mColorDrawable); + mBackground.setColor(color); if (mNotificationInfo != null) { mIconView.setBackground(mNotificationInfo.getIconForBackground(getContext(), mBackgroundColor)); @@ -128,8 +185,11 @@ public class NotificationMainView extends FrameLayout { /** * Sets the content of this view, animating it after a new icon shifts up if necessary. */ - public void applyNotificationInfo(NotificationInfo mainNotification, boolean animate) { - mNotificationInfo = mainNotification; + public void applyNotificationInfo(NotificationInfo notificationInfo) { + mNotificationInfo = notificationInfo; + if (notificationInfo == null) { + return; + } NotificationListener listener = NotificationListener.getInstanceIfConnected(); if (listener != null) { listener.setNotificationsShown(new String[] {mNotificationInfo.notificationKey}); @@ -149,25 +209,112 @@ public class NotificationMainView extends FrameLayout { if (mNotificationInfo.intent != null) { setOnClickListener(mNotificationInfo); } - setContentTranslation(0); + // Add a stub ItemInfo so that logging populates the correct container and item types // instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively. setTag(NOTIFICATION_ITEM_INFO); - if (animate) { - ObjectAnimator.ofFloat(mTextAndBackground, ALPHA, 0, 1).setDuration(150).start(); + } + + /** + * Sets the alpha of only the child views. + */ + public void setContentAlpha(float alpha) { + mHeader.setAlpha(alpha); + mMainView.setAlpha(alpha); + } + + /** + * Sets the translation of only the child views. + */ + public void setContentTranslationX(float transX) { + mHeader.setTranslationX(transX); + mMainView.setTranslationX(transX); + } + + /** + * Updates the alpha, content alpha, and elevation of this view. + * + * @param progress Range from [0, 1] or [-1, 0] + * When 0: Full alpha + * When 1/-1: zero alpha + */ + public void onPrimaryDrag(float progress) { + float absProgress = Math.abs(progress); + final int width = getWidth(); + + float min = PRIMARY_MIN_PROGRESS; + float max = PRIMARY_MAX_PROGRESS; + + if (absProgress < min) { + setAlpha(1f); + setContentAlpha(1); + setElevation(mMaxElevation); + } else if (absProgress < max) { + setAlpha(1f); + setContentAlpha(mapToRange(absProgress, min, max, 1f, 0f, LINEAR)); + setElevation(Utilities.mapToRange(absProgress, min, max, mMaxElevation, 0, LINEAR)); + } else { + setAlpha(mapToRange(absProgress, max, PRIMARY_GONE_PROGRESS, 1f, 0f, LINEAR)); + setContentAlpha(0f); + setElevation(0f); } + + setTranslationX(width * progress); } - public void setContentTranslation(float translation) { - mTextAndBackground.setTranslationX(translation); - mIconView.setTranslationX(translation); + /** + * Updates the alpha, content alpha, elevation, and clipping of this view. + * @param progress Range from [0, 1] or [-1, 0] + * When 0: Smallest clipping, zero alpha + * When 1/-1: Full clip, full alpha + */ + public void onSecondaryDrag(float progress) { + final float absProgress = Math.abs(progress); + + float min = SECONDARY_MIN_PROGRESS; + float max = SECONDARY_MAX_PROGRESS; + float contentMax = SECONDARY_CONTENT_MAX_PROGRESS; + + if (absProgress < min) { + setAlpha(0f); + setContentAlpha(0); + setElevation(0f); + } else if (absProgress < max) { + setAlpha(mapToRange(absProgress, min, max, 0, 1f, LINEAR)); + setContentAlpha(0f); + setElevation(0f); + } else { + setAlpha(1f); + setContentAlpha(absProgress > contentMax + ? 1f + : mapToRange(absProgress, max, contentMax, 0, 1f, LINEAR)); + setElevation(Utilities.mapToRange(absProgress, max, 1, 0, mMaxElevation, LINEAR)); + } + + final int width = getWidth(); + int crop = (int) (width * absProgress); + int space = (int) (absProgress > PRIMARY_GONE_PROGRESS + ? mapToRange(absProgress, PRIMARY_GONE_PROGRESS, 1f, mNotificationSpace, 0, LINEAR) + : mNotificationSpace); + if (progress < 0) { + mOutline.left = Math.max(0, getWidth() - crop + space); + mOutline.right = getWidth(); + } else { + mOutline.right = Math.min(getWidth(), crop - space); + mOutline.left = 0; + } + + float contentTransX = mMaxTransX * (1f - absProgress); + setContentTranslationX(progress < 0 + ? contentTransX + : -contentTransX); + invalidateOutline(); } - public NotificationInfo getNotificationInfo() { + public @Nullable NotificationInfo getNotificationInfo() { return mNotificationInfo; } - public boolean canChildBeDismissed() { return mNotificationInfo != null && mNotificationInfo.dismissable; } diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java index a534ee3eb4..5d3ba75bc0 100644 --- a/src/com/android/launcher3/popup/ArrowPopup.java +++ b/src/com/android/launcher3/popup/ArrowPopup.java @@ -487,6 +487,13 @@ public abstract class ArrowPopup> return getMeasuredWidth() - mArrowOffsetHorizontal - mArrowWidth; } + /** + * @param show If true, shows arrow (when applicable), otherwise hides arrow. + */ + public void showArrow(boolean show) { + mArrow.setVisibility(show && shouldAddArrow() ? VISIBLE : INVISIBLE); + } + private void addArrow() { getPopupContainer().addView(mArrow); mArrow.setX(getX() + getArrowLeft()); diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 18f263a8ec..bc3419aa81 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -58,8 +58,8 @@ import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.notification.NotificationContainer; import com.android.launcher3.notification.NotificationInfo; -import com.android.launcher3.notification.NotificationItemView; import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener; import com.android.launcher3.shortcuts.DeepShortcutView; @@ -92,9 +92,8 @@ public class PopupContainerWithArrow> private final int mStartDragThreshold; private BubbleTextView mOriginalIcon; - private NotificationItemView mNotificationItemView; private int mNumNotifications; - private ViewGroup mNotificationContainer; + private NotificationContainer mNotificationContainer; private ViewGroup mWidgetContainer; @@ -128,8 +127,8 @@ public class PopupContainerWithArrow> if (ev.getAction() == MotionEvent.ACTION_DOWN) { mInterceptTouchDown.set(ev.getX(), ev.getY()); } - if (mNotificationItemView != null - && mNotificationItemView.onInterceptTouchEvent(ev)) { + if (mNotificationContainer != null + && mNotificationContainer.onInterceptSwipeEvent(ev)) { return true; } // Stop sending touch events to deep shortcut views if user moved beyond touch slop. @@ -137,6 +136,14 @@ public class PopupContainerWithArrow> > squaredTouchSlop(getContext()); } + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (mNotificationContainer != null) { + return mNotificationContainer.onSwipeEvent(ev) || super.onTouchEvent(ev); + } + return super.onTouchEvent(ev); + } + @Override protected boolean isOfType(int type) { return (type & TYPE_ACTION_POPUP) != 0; @@ -172,8 +179,8 @@ public class PopupContainerWithArrow> @Override protected void setChildColor(View view, int color, AnimatorSet animatorSetOut) { super.setChildColor(view, color, animatorSetOut); - if (view.getId() == R.id.notification_container && mNotificationItemView != null) { - mNotificationItemView.updateBackgroundColor(color, animatorSetOut); + if (view.getId() == R.id.notification_container && mNotificationContainer != null) { + mNotificationContainer.updateBackgroundColor(color, animatorSetOut); } } @@ -232,13 +239,6 @@ public class PopupContainerWithArrow> mNotificationContainer); } - @Override - protected void onInflationComplete(boolean isReversed) { - if (isReversed && mNotificationItemView != null) { - mNotificationItemView.inverseGutterMargin(); - } - } - @TargetApi(Build.VERSION_CODES.P) public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount, final List notificationKeys, List systemShortcuts) { @@ -261,9 +261,10 @@ public class PopupContainerWithArrow> if (mNotificationContainer == null) { mNotificationContainer = findViewById(R.id.notification_container); mNotificationContainer.setVisibility(VISIBLE); + mNotificationContainer.setPopupView(this); + } else { + mNotificationContainer.setVisibility(GONE); } - View.inflate(getContext(), R.layout.notification_content, mNotificationContainer); - mNotificationItemView = new NotificationItemView(this, mNotificationContainer); updateNotificationHeader(); } int viewsToFlip = getChildCount(); @@ -274,10 +275,6 @@ public class PopupContainerWithArrow> if (hasDeepShortcuts) { mDeepShortcutContainer.setVisibility(View.VISIBLE); - if (mNotificationItemView != null) { - mNotificationItemView.addGutter(); - } - for (int i = shortcutCount; i > 0; i--) { DeepShortcutView v = inflateAndAdd(R.layout.deep_shortcut, mDeepShortcutContainer); v.getLayoutParams().width = containerWidth; @@ -309,10 +306,6 @@ public class PopupContainerWithArrow> } else { mDeepShortcutContainer.setVisibility(View.GONE); if (!systemShortcuts.isEmpty()) { - if (mNotificationItemView != null) { - mNotificationItemView.addGutter(); - } - for (SystemShortcut shortcut : systemShortcuts) { initializeSystemShortcut(R.layout.system_shortcut, this, shortcut); } @@ -355,13 +348,13 @@ public class PopupContainerWithArrow> } public void applyNotificationInfos(List notificationInfos) { - if (mNotificationItemView != null) { - mNotificationItemView.applyNotificationInfos(notificationInfos); + if (mNotificationContainer != null) { + mNotificationContainer.applyNotificationInfos(notificationInfos); } } private void updateHiddenShortcuts() { - int allowedCount = mNotificationItemView != null + int allowedCount = mNotificationContainer != null ? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS; int total = mShortcuts.size(); @@ -447,8 +440,8 @@ public class PopupContainerWithArrow> private void updateNotificationHeader() { ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag(); DotInfo dotInfo = mLauncher.getDotInfoForItem(itemInfo); - if (mNotificationItemView != null && dotInfo != null) { - mNotificationItemView.updateHeader(dotInfo.getNotificationCount()); + if (mNotificationContainer != null && dotInfo != null) { + mNotificationContainer.updateHeader(dotInfo.getNotificationCount()); } } @@ -590,20 +583,18 @@ public class PopupContainerWithArrow> @Override public void trimNotifications(Map updatedDots) { - if (mNotificationItemView == null) { + if (mNotificationContainer == null) { return; } ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag(); DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo)); if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) { // No more notifications, remove the notification views and expand all shortcuts. - mNotificationItemView.removeAllViews(); - mNotificationItemView = null; mNotificationContainer.setVisibility(GONE); updateHiddenShortcuts(); assignMarginsAndBackgrounds(PopupContainerWithArrow.this); } else { - mNotificationItemView.trimNotifications( + mNotificationContainer.trimNotifications( NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys())); } }