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