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
This commit is contained in:
committed by
Jonathan Miranda
parent
6e72c8bbba
commit
f3bbd98bf8
@@ -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<NotificationContainer> DRAG_TRANSLATION_X =
|
||||
new FloatProperty<NotificationContainer>("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<NotificationInfo> 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<NotificationInfo> 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<String> notificationKeys) {
|
||||
Iterator<NotificationInfo> 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user