summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/notification/NotificationFooterLayout.java
diff options
context:
space:
mode:
authorTony Wickham <twickham@google.com>2017-01-20 09:38:25 -0800
committerTony Wickham <twickham@google.com>2017-01-25 17:36:31 -0800
commit9438ed414fdabadb4cd09da184867b1c44b91095 (patch)
tree9d84de236a89cde247b9de8f11408e815e6d6702 /src/com/android/launcher3/notification/NotificationFooterLayout.java
parentf3d02e4716f89d14d9017851db9ad6141ad26875 (diff)
downloadandroid_packages_apps_Trebuchet-9438ed414fdabadb4cd09da184867b1c44b91095.tar.gz
android_packages_apps_Trebuchet-9438ed414fdabadb4cd09da184867b1c44b91095.tar.bz2
android_packages_apps_Trebuchet-9438ed414fdabadb4cd09da184867b1c44b91095.zip
Add swipe-to-dismiss notifications in popup menu.
- Next secondary icon animates up to replace dismissed main notification - Add padding around main notification so it always aligns with the straight edges of the view (not the rounded corners); looks more dismissable - Notification view collapses as notifications are dismissed - To mimic system notification behavior, we copy SwipeHelper, FlingAnimationUtils, and Interpolators. We also apply elevation to notifications and reveal a darker color beneath when dismissing. Bug: 32410600 Change-Id: I9fbf10e73bb4996f17ef061c856efb013967d972
Diffstat (limited to 'src/com/android/launcher3/notification/NotificationFooterLayout.java')
-rw-r--r--src/com/android/launcher3/notification/NotificationFooterLayout.java244
1 files changed, 244 insertions, 0 deletions
diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java
new file mode 100644
index 000000000..2965e4a63
--- /dev/null
+++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java
@@ -0,0 +1,244 @@
+/*
+ * 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.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.LauncherViewPropertyAnimator;
+import com.android.launcher3.R;
+import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A {@link LinearLayout} that contains icons of notifications. If there is only one icon,
+ * we also supply the notification text/secondary text like we do for the main notification.
+ * If there are more than {@link #MAX_FOOTER_NOTIFICATIONS} icons, we add a "+x" overflow.
+ */
+public class NotificationFooterLayout extends LinearLayout {
+
+ public interface IconAnimationEndListener {
+ void onIconAnimationEnd(NotificationInfo animatedNotification);
+ }
+
+ private static final int MAX_FOOTER_NOTIFICATIONS = 5;
+
+ private static final Rect sTempRect = new Rect();
+
+ private final List<NotificationInfo> mNotifications = new ArrayList<>();
+ private final List<NotificationInfo> mOverflowNotifications = new ArrayList<>();
+ private final Map<View, NotificationInfo> mViewsToInfos = new HashMap<>();
+
+ LinearLayout.LayoutParams mIconLayoutParams;
+ private LinearLayout mIconRow;
+ private int mTextColor;
+
+ public NotificationFooterLayout(Context context) {
+ this(context, null, 0);
+ }
+
+ public NotificationFooterLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NotificationFooterLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ int size = getResources().getDimensionPixelSize(
+ R.dimen.notification_footer_icon_size);
+ int padding = getResources().getDimensionPixelSize(
+ R.dimen.deep_shortcut_drawable_padding);
+ mIconLayoutParams = new LayoutParams(size, size);
+ mIconLayoutParams.setMarginStart(padding);
+ mIconLayoutParams.gravity = Gravity.CENTER_VERTICAL;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mIconRow = (LinearLayout) findViewById(R.id.icon_row);
+ }
+
+ public void applyColors(IconPalette iconPalette) {
+ setBackgroundTintList(ColorStateList.valueOf(iconPalette.backgroundColor));
+ findViewById(R.id.divider).setBackgroundColor(iconPalette.secondaryColor);
+ mTextColor = iconPalette.textColor;
+ }
+
+ /**
+ * Keep track of the NotificationInfo, and then update the UI when
+ * {@link #commitNotificationInfos()} is called.
+ */
+ public void addNotificationInfo(final NotificationInfo notificationInfo) {
+ if (mNotifications.size() < MAX_FOOTER_NOTIFICATIONS) {
+ mNotifications.add(notificationInfo);
+ } else {
+ mOverflowNotifications.add(notificationInfo);
+ }
+ }
+
+ /**
+ * Adds icons and potentially overflow text for all of the NotificationInfo's
+ * added using {@link #addNotificationInfo(NotificationInfo)}.
+ */
+ public void commitNotificationInfos() {
+ mIconRow.removeAllViews();
+ mViewsToInfos.clear();
+
+ for (int i = 0; i < mNotifications.size(); i++) {
+ NotificationInfo info = mNotifications.get(i);
+ addNotificationIconForInfo(info, false /* fromOverflow */);
+ }
+
+ if (!mOverflowNotifications.isEmpty()) {
+ TextView overflowText = new TextView(getContext());
+ overflowText.setTextColor(mTextColor);
+ updateOverflowText(overflowText);
+ mIconRow.addView(overflowText, mIconLayoutParams);
+ }
+ }
+
+ private void addNotificationIconForInfo(NotificationInfo info, boolean fromOverflow) {
+ View icon = new View(getContext());
+ icon.setBackground(info.iconDrawable);
+ icon.setOnClickListener(info);
+ int addIndex = mIconRow.getChildCount();
+ if (fromOverflow) {
+ // Add the notification before the overflow view.
+ addIndex--;
+ icon.setAlpha(0);
+ icon.animate().alpha(1);
+ }
+ mIconRow.addView(icon, addIndex, mIconLayoutParams);
+ mViewsToInfos.put(icon, info);
+ }
+
+ private void updateOverflowText(TextView overflowTextView) {
+ overflowTextView.setText(getResources().getString(R.string.deep_notifications_overflow,
+ mOverflowNotifications.size()));
+ }
+
+ public void animateFirstNotificationTo(Rect toBounds,
+ final IconAnimationEndListener callback) {
+ AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
+ final View firstNotification = mIconRow.getChildAt(0);
+
+ Rect fromBounds = sTempRect;
+ firstNotification.getGlobalVisibleRect(fromBounds);
+ float scale = (float) toBounds.height() / fromBounds.height();
+ Animator moveAndScaleIcon = new LauncherViewPropertyAnimator(firstNotification)
+ .translationY(toBounds.top - fromBounds.top
+ + (fromBounds.height() * scale - fromBounds.height()) / 2)
+ .scaleX(scale).scaleY(scale);
+ moveAndScaleIcon.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ callback.onIconAnimationEnd(mViewsToInfos.get(firstNotification));
+ }
+ });
+ animation.play(moveAndScaleIcon);
+
+ // Shift all notifications (not the overflow) over to fill the gap.
+ int gapWidth = mIconLayoutParams.width + mIconLayoutParams.getMarginStart();
+ int numIcons = mIconRow.getChildCount()
+ - (mOverflowNotifications.isEmpty() ? 0 : 1);
+ for (int i = 1; i < numIcons; i++) {
+ final View child = mIconRow.getChildAt(i);
+ Animator shiftChild = new LauncherViewPropertyAnimator(child).translationX(-gapWidth);
+ shiftChild.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // We have to set the translation X to 0 when the new main notification
+ // is removed from the footer.
+ // TODO: remove it here instead of expecting trimNotifications to do so.
+ child.setTranslationX(0);
+ }
+ });
+ animation.play(shiftChild);
+ }
+ animation.start();
+ }
+
+ public void trimNotifications(Set<String> notifications) {
+ if (!isAttachedToWindow() || mIconRow.getChildCount() == 0) {
+ return;
+ }
+ Iterator<NotificationInfo> overflowIterator = mOverflowNotifications.iterator();
+ while (overflowIterator.hasNext()) {
+ if (!notifications.contains(overflowIterator.next().notificationKey)) {
+ overflowIterator.remove();
+ }
+ }
+ TextView overflowView = null;
+ for (int i = mIconRow.getChildCount() - 1; i >= 0; i--) {
+ View child = mIconRow.getChildAt(i);
+ if (child instanceof TextView) {
+ overflowView = (TextView) child;
+ } else {
+ NotificationInfo childInfo = mViewsToInfos.get(child);
+ if (!notifications.contains(childInfo.notificationKey)) {
+ mIconRow.removeView(child);
+ mNotifications.remove(childInfo);
+ mViewsToInfos.remove(child);
+ if (!mOverflowNotifications.isEmpty()) {
+ NotificationInfo notification = mOverflowNotifications.remove(0);
+ mNotifications.add(notification);
+ addNotificationIconForInfo(notification, true /* fromOverflow */);
+ }
+ }
+ }
+ }
+ if (overflowView != null) {
+ if (mOverflowNotifications.isEmpty()) {
+ mIconRow.removeView(overflowView);
+ } else {
+ updateOverflowText(overflowView);
+ }
+ }
+ if (mIconRow.getChildCount() == 0) {
+ // There are no more icons in the secondary view, so hide it.
+ PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(
+ Launcher.getLauncher(getContext()));
+ int newHeight = getResources().getDimensionPixelSize(
+ R.dimen.notification_footer_collapsed_height);
+ AnimatorSet collapseSecondary = LauncherAnimUtils.createAnimatorSet();
+ collapseSecondary.play(popup.animateTranslationYBy(getHeight() - newHeight,
+ getResources().getInteger(R.integer.config_removeNotificationViewDuration)));
+ collapseSecondary.play(LauncherAnimUtils.animateViewHeight(
+ this, getHeight(), newHeight));
+ collapseSecondary.start();
+ }
+ }
+}