/* * 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 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.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; } }