1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.plugin.globalactions.wallet; 18 19 import static com.android.systemui.plugin.globalactions.wallet.WalletCardCarousel.CARD_ANIM_ALPHA_DELAY; 20 import static com.android.systemui.plugin.globalactions.wallet.WalletCardCarousel.CARD_ANIM_ALPHA_DURATION; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorListenerAdapter; 24 import android.annotation.Nullable; 25 import android.content.Context; 26 import android.graphics.drawable.Drawable; 27 import android.text.TextUtils; 28 import android.util.AttributeSet; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.view.animation.AnimationUtils; 33 import android.view.animation.Interpolator; 34 import android.widget.FrameLayout; 35 import android.widget.ImageView; 36 import android.widget.ListPopupWindow; 37 import android.widget.TextView; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.systemui.plugin.globalactions.wallet.WalletPopupMenu.OverflowItem; 41 42 import java.util.List; 43 44 45 /** 46 * Contains the entire wallet view. Coordinates the label text with the currently selected card in 47 * the contained carousel. Also capable of showing the lock screen error message. 48 */ 49 class WalletView extends FrameLayout implements WalletCardCarousel.OnCardScrollListener { 50 51 private static final int CAROUSEL_IN_ANIMATION_DURATION = 300; 52 private static final int CAROUSEL_OUT_ANIMATION_DURATION = 200; 53 private static final int CARD_LABEL_ANIM_DELAY = 133; 54 55 private final ViewGroup mCardCarouselContainer; 56 private final WalletCardCarousel mCardCarousel; 57 private final TextView mCardLabel; 58 private final TextView mErrorView; 59 private final ViewGroup mEmptyStateView; 60 private final ImageView mOverflowButton; 61 private final WalletPopupMenu mOverflowPopup; 62 private final int mIconSizePx; 63 private final Interpolator mInInterpolator; 64 private final Interpolator mOutInterpolator; 65 private final float mAnimationTranslationX; 66 private CharSequence mCenterCardText; 67 WalletView(Context context)68 public WalletView(Context context) { 69 this(context, null); 70 } 71 WalletView(Context context, AttributeSet attrs)72 public WalletView(Context context, AttributeSet attrs) { 73 super(context, attrs); 74 inflate(context, R.layout.wallet_view, this); 75 mCardCarouselContainer = requireViewById(R.id.card_carousel_container); 76 mCardCarousel = requireViewById(R.id.card_carousel); 77 mCardCarousel.setCardScrollListener(this); 78 mCardLabel = requireViewById(R.id.card_label); 79 mOverflowButton = requireViewById(R.id.menu_btn); 80 mOverflowPopup = new WalletPopupMenu(context, mOverflowButton); 81 mOverflowButton.setOnClickListener(v -> mOverflowPopup.show()); 82 mErrorView = requireViewById(R.id.error_view); 83 mEmptyStateView = requireViewById(R.id.empty_state); 84 mIconSizePx = getResources().getDimensionPixelSize(R.dimen.icon_size); 85 mInInterpolator = 86 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in); 87 mOutInterpolator = 88 AnimationUtils.loadInterpolator(context, android.R.interpolator.accelerate_cubic); 89 mAnimationTranslationX = mCardCarousel.getCardWidthPx() / 4f; 90 } 91 92 @Override onTouchEvent(MotionEvent event)93 public boolean onTouchEvent(MotionEvent event) { 94 // Forward touch events to card carousel to allow for swiping outside carousel bounds. 95 return mCardCarousel.onTouchEvent(event) || super.onTouchEvent(event); 96 } 97 98 @Override onCardScroll(WalletCardViewInfo centerCard, WalletCardViewInfo nextCard, float percentDistanceFromCenter)99 public void onCardScroll(WalletCardViewInfo centerCard, WalletCardViewInfo nextCard, 100 float percentDistanceFromCenter) { 101 CharSequence centerCardText = centerCard.getText(); 102 if (!TextUtils.equals(mCenterCardText, centerCardText)) { 103 mCenterCardText = centerCardText; 104 mCardLabel.setText(centerCardText); 105 Drawable icon = centerCard.getIcon(); 106 if (icon != null) { 107 icon.setBounds(0, 0, mIconSizePx, mIconSizePx); 108 } 109 mCardLabel.setCompoundDrawablesRelative(icon, null, null, null); 110 } 111 if (TextUtils.equals(centerCardText, nextCard.getText())) { 112 mCardLabel.setAlpha(1f); 113 } else { 114 mCardLabel.setAlpha(percentDistanceFromCenter); 115 } 116 } 117 showCardCarousel( List<WalletCardViewInfo> data, int selectedIndex, OverflowItem[] menuItems)118 void showCardCarousel( 119 List<WalletCardViewInfo> data, int selectedIndex, OverflowItem[] menuItems) { 120 boolean shouldAnimate = mCardCarousel.setData(data, selectedIndex); 121 mOverflowPopup.setMenuItems(menuItems); 122 mOverflowButton.setVisibility(menuItems.length == 0 ? GONE : VISIBLE); 123 mCardCarouselContainer.setVisibility(VISIBLE); 124 mErrorView.setVisibility(GONE); 125 if (shouldAnimate) { 126 // If the empty state is visible, animate it away and delay the card carousel animation 127 int emptyStateAnimDelay = 0; 128 if (mEmptyStateView.getVisibility() == VISIBLE) { 129 emptyStateAnimDelay = CARD_ANIM_ALPHA_DURATION; 130 mEmptyStateView.animate() 131 .alpha(0) 132 .setDuration(emptyStateAnimDelay) 133 .setListener(new AnimatorListenerAdapter() { 134 @Override 135 public void onAnimationEnd(Animator animation) { 136 mEmptyStateView.setVisibility(GONE); 137 } 138 }) 139 .start(); 140 } 141 142 mCardLabel.setAlpha(0f); 143 mCardLabel.animate().alpha(1f) 144 .setStartDelay(CARD_LABEL_ANIM_DELAY + emptyStateAnimDelay) 145 .setDuration(CARD_ANIM_ALPHA_DURATION) 146 .start(); 147 mOverflowButton.setAlpha(0f); 148 mOverflowButton.animate().alpha(1f) 149 .setStartDelay(CARD_LABEL_ANIM_DELAY + emptyStateAnimDelay) 150 .setDuration(CARD_ANIM_ALPHA_DURATION) 151 .start(); 152 mCardCarousel.setExtraAnimationDelay(emptyStateAnimDelay); 153 mCardCarousel.setTranslationX(mAnimationTranslationX); 154 mCardCarousel.animate().translationX(0) 155 .setInterpolator(mInInterpolator) 156 .setDuration(CAROUSEL_IN_ANIMATION_DURATION) 157 .setStartDelay(emptyStateAnimDelay) 158 .start(); 159 } 160 } 161 animateDismissal()162 void animateDismissal() { 163 if (mCardCarouselContainer.getVisibility() != VISIBLE) { 164 return; 165 } 166 mOverflowPopup.dismiss(); 167 mCardCarousel.animate().translationX(mAnimationTranslationX) 168 .setInterpolator(mOutInterpolator) 169 .setDuration(CAROUSEL_OUT_ANIMATION_DURATION) 170 .start(); 171 mCardCarouselContainer.animate() 172 .alpha(0f) 173 .setDuration(CARD_ANIM_ALPHA_DURATION) 174 .setStartDelay(CARD_ANIM_ALPHA_DELAY) 175 .start(); 176 } 177 showEmptyStateView(Drawable logo, CharSequence logoContentDescription, CharSequence label, OnClickListener clickListener)178 void showEmptyStateView(Drawable logo, CharSequence logoContentDescription, CharSequence label, 179 OnClickListener clickListener) { 180 mEmptyStateView.setVisibility(VISIBLE); 181 mErrorView.setVisibility(GONE); 182 mCardCarouselContainer.setVisibility(GONE); 183 ImageView logoView = mEmptyStateView.requireViewById(R.id.icon); 184 logoView.setImageDrawable(logo); 185 logoView.setContentDescription(logoContentDescription); 186 mEmptyStateView.<TextView>requireViewById(R.id.title).setText(label); 187 mEmptyStateView.setOnClickListener(clickListener); 188 } 189 showDeviceLockedMessage()190 void showDeviceLockedMessage() { 191 showErrorMessage(getResources().getText(R.string.error_user_locked)); 192 } 193 showErrorMessage(@ullable CharSequence message)194 void showErrorMessage(@Nullable CharSequence message) { 195 if (TextUtils.isEmpty(message)) { 196 message = getResources().getText(R.string.error_generic); 197 } 198 mErrorView.setText(message); 199 mErrorView.setVisibility(VISIBLE); 200 mCardCarouselContainer.setVisibility(GONE); 201 mEmptyStateView.setVisibility(GONE); 202 } 203 hide()204 void hide() { 205 setVisibility(GONE); 206 } 207 show()208 void show() { 209 setVisibility(VISIBLE); 210 } 211 hideErrorMessage()212 void hideErrorMessage() { 213 mErrorView.setVisibility(GONE); 214 } 215 getIconSizePx()216 int getIconSizePx() { 217 return mIconSizePx; 218 } 219 getCardCarousel()220 WalletCardCarousel getCardCarousel() { 221 return mCardCarousel; 222 } 223 224 @VisibleForTesting getErrorView()225 TextView getErrorView() { 226 return mErrorView; 227 } 228 229 @VisibleForTesting getEmptyStateView()230 ViewGroup getEmptyStateView() { 231 return mEmptyStateView; 232 } 233 234 @VisibleForTesting getCardCarouselContainer()235 ViewGroup getCardCarouselContainer() { 236 return mCardCarouselContainer; 237 } 238 239 @VisibleForTesting getOverflowIcon()240 View getOverflowIcon() { 241 return mOverflowButton; 242 } 243 244 @VisibleForTesting getOverflowPopup()245 ListPopupWindow getOverflowPopup() { 246 return mOverflowPopup; 247 } 248 } 249