blob: a0231ec40ebe2f46ffcf85e943ce364968e98a5d [file] [log] [blame]
/*
* Copyright (C) 2020 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.systemui.plugin.globalactions.wallet;
import static com.android.systemui.plugin.globalactions.wallet.WalletCardCarousel.CARD_ANIM_ALPHA_DELAY;
import static com.android.systemui.plugin.globalactions.wallet.WalletCardCarousel.CARD_ANIM_ALPHA_DURATION;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListPopupWindow;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.plugin.globalactions.wallet.WalletPopupMenu.OverflowItem;
import java.util.List;
/**
* Contains the entire wallet view. Coordinates the label text with the currently selected card in
* the contained carousel. Also capable of showing the lock screen error message.
*/
class WalletView extends FrameLayout implements WalletCardCarousel.OnCardScrollListener {
private static final int CAROUSEL_IN_ANIMATION_DURATION = 300;
private static final int CAROUSEL_OUT_ANIMATION_DURATION = 200;
private static final int CARD_LABEL_ANIM_DELAY = 133;
private final ViewGroup mCardCarouselContainer;
private final WalletCardCarousel mCardCarousel;
private final TextView mCardLabel;
private final TextView mErrorView;
private final ViewGroup mEmptyStateView;
private final ImageView mOverflowButton;
private final WalletPopupMenu mOverflowPopup;
private final int mIconSizePx;
private final Interpolator mInInterpolator;
private final Interpolator mOutInterpolator;
private final float mAnimationTranslationX;
private CharSequence mCenterCardText;
public WalletView(Context context) {
this(context, null);
}
public WalletView(Context context, AttributeSet attrs) {
super(context, attrs);
inflate(context, R.layout.wallet_view, this);
mCardCarouselContainer = requireViewById(R.id.card_carousel_container);
mCardCarousel = requireViewById(R.id.card_carousel);
mCardCarousel.setCardScrollListener(this);
mCardLabel = requireViewById(R.id.card_label);
mOverflowButton = requireViewById(R.id.menu_btn);
mOverflowPopup = new WalletPopupMenu(context, mOverflowButton);
mOverflowButton.setOnClickListener(v -> mOverflowPopup.show());
mErrorView = requireViewById(R.id.error_view);
mEmptyStateView = requireViewById(R.id.empty_state);
mIconSizePx = getResources().getDimensionPixelSize(R.dimen.icon_size);
mInInterpolator =
AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
mOutInterpolator =
AnimationUtils.loadInterpolator(context, android.R.interpolator.accelerate_cubic);
mAnimationTranslationX = mCardCarousel.getCardWidthPx() / 4f;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Forward touch events to card carousel to allow for swiping outside carousel bounds.
return mCardCarousel.onTouchEvent(event) || super.onTouchEvent(event);
}
@Override
public void onCardScroll(WalletCardViewInfo centerCard, WalletCardViewInfo nextCard,
float percentDistanceFromCenter) {
CharSequence centerCardText = centerCard.getText();
if (!TextUtils.equals(mCenterCardText, centerCardText)) {
mCenterCardText = centerCardText;
mCardLabel.setText(centerCardText);
Drawable icon = centerCard.getIcon();
if (icon != null) {
icon.setBounds(0, 0, mIconSizePx, mIconSizePx);
}
mCardLabel.setCompoundDrawablesRelative(icon, null, null, null);
}
if (TextUtils.equals(centerCardText, nextCard.getText())) {
mCardLabel.setAlpha(1f);
} else {
mCardLabel.setAlpha(percentDistanceFromCenter);
}
}
void showCardCarousel(
List<WalletCardViewInfo> data, int selectedIndex, OverflowItem[] menuItems) {
boolean shouldAnimate = mCardCarousel.setData(data, selectedIndex);
mOverflowPopup.setMenuItems(menuItems);
mOverflowButton.setVisibility(menuItems.length == 0 ? GONE : VISIBLE);
mCardCarouselContainer.setVisibility(VISIBLE);
mErrorView.setVisibility(GONE);
if (shouldAnimate) {
// If the empty state is visible, animate it away and delay the card carousel animation
int emptyStateAnimDelay = 0;
if (mEmptyStateView.getVisibility() == VISIBLE) {
emptyStateAnimDelay = CARD_ANIM_ALPHA_DURATION;
mEmptyStateView.animate()
.alpha(0)
.setDuration(emptyStateAnimDelay)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mEmptyStateView.setVisibility(GONE);
}
})
.start();
}
mCardLabel.setAlpha(0f);
mCardLabel.animate().alpha(1f)
.setStartDelay(CARD_LABEL_ANIM_DELAY + emptyStateAnimDelay)
.setDuration(CARD_ANIM_ALPHA_DURATION)
.start();
mOverflowButton.setAlpha(0f);
mOverflowButton.animate().alpha(1f)
.setStartDelay(CARD_LABEL_ANIM_DELAY + emptyStateAnimDelay)
.setDuration(CARD_ANIM_ALPHA_DURATION)
.start();
mCardCarousel.setExtraAnimationDelay(emptyStateAnimDelay);
mCardCarousel.setTranslationX(mAnimationTranslationX);
mCardCarousel.animate().translationX(0)
.setInterpolator(mInInterpolator)
.setDuration(CAROUSEL_IN_ANIMATION_DURATION)
.setStartDelay(emptyStateAnimDelay)
.start();
}
}
void animateDismissal() {
if (mCardCarouselContainer.getVisibility() != VISIBLE) {
return;
}
mOverflowPopup.dismiss();
mCardCarousel.animate().translationX(mAnimationTranslationX)
.setInterpolator(mOutInterpolator)
.setDuration(CAROUSEL_OUT_ANIMATION_DURATION)
.start();
mCardCarouselContainer.animate()
.alpha(0f)
.setDuration(CARD_ANIM_ALPHA_DURATION)
.setStartDelay(CARD_ANIM_ALPHA_DELAY)
.start();
}
void showEmptyStateView(Drawable logo, CharSequence logoContentDescription, CharSequence label,
OnClickListener clickListener) {
mEmptyStateView.setVisibility(VISIBLE);
mErrorView.setVisibility(GONE);
mCardCarouselContainer.setVisibility(GONE);
ImageView logoView = mEmptyStateView.requireViewById(R.id.icon);
logoView.setImageDrawable(logo);
logoView.setContentDescription(logoContentDescription);
mEmptyStateView.<TextView>requireViewById(R.id.title).setText(label);
mEmptyStateView.setOnClickListener(clickListener);
}
void showDeviceLockedMessage() {
showErrorMessage(getResources().getText(R.string.error_user_locked));
}
void showErrorMessage(@Nullable CharSequence message) {
if (TextUtils.isEmpty(message)) {
message = getResources().getText(R.string.error_generic);
}
mErrorView.setText(message);
mErrorView.setVisibility(VISIBLE);
mCardCarouselContainer.setVisibility(GONE);
mEmptyStateView.setVisibility(GONE);
}
void hide() {
setVisibility(GONE);
}
void show() {
setVisibility(VISIBLE);
}
void hideErrorMessage() {
mErrorView.setVisibility(GONE);
}
int getIconSizePx() {
return mIconSizePx;
}
WalletCardCarousel getCardCarousel() {
return mCardCarousel;
}
@VisibleForTesting
TextView getErrorView() {
return mErrorView;
}
@VisibleForTesting
ViewGroup getEmptyStateView() {
return mEmptyStateView;
}
@VisibleForTesting
ViewGroup getCardCarouselContainer() {
return mCardCarouselContainer;
}
@VisibleForTesting
View getOverflowIcon() {
return mOverflowButton;
}
@VisibleForTesting
ListPopupWindow getOverflowPopup() {
return mOverflowPopup;
}
}