blob: d11af88a0c9e2dc4490455ae68ac99f33ca63b4a [file] [log] [blame]
Chuck Liao8ec38e02020-02-26 20:59:32 +08001/*
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 */
16package com.android.wallpaper.widget;
17
chihhangchuang22aa0cc2020-03-25 19:12:42 +080018import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
19import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED;
20
chihhangchuang7feb3752020-04-24 02:48:56 +080021import android.app.Activity;
Chuck Liao8ec38e02020-02-26 20:59:32 +080022import android.content.Context;
23import android.util.AttributeSet;
24import android.view.LayoutInflater;
25import android.view.View;
chihhangchuang22aa0cc2020-03-25 19:12:42 +080026import android.view.ViewGroup;
Chuck Liao8ec38e02020-02-26 20:59:32 +080027import android.widget.FrameLayout;
28
29import androidx.annotation.NonNull;
30import androidx.annotation.Nullable;
31
32import com.android.wallpaper.R;
Santiago Etchebehere53c63432020-05-07 18:55:35 -070033import com.android.wallpaper.util.SizeCalculator;
Chuck Liao8ec38e02020-02-26 20:59:32 +080034
chihhangchuang22aa0cc2020-03-25 19:12:42 +080035import com.google.android.material.bottomsheet.BottomSheetBehavior;
36import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback;
37
Chihhang Chuang541e0752020-06-11 19:14:40 +080038import java.util.ArrayDeque;
chihhangchuang803ea9a2020-04-21 13:03:10 +080039import java.util.Arrays;
Chihhang Chuang541e0752020-06-11 19:14:40 +080040import java.util.Deque;
Chuck Liao8ec38e02020-02-26 20:59:32 +080041import java.util.EnumMap;
chihhangchuang803ea9a2020-04-21 13:03:10 +080042import java.util.HashSet;
Chuck Liao8ec38e02020-02-26 20:59:32 +080043import java.util.Map;
chihhangchuang803ea9a2020-04-21 13:03:10 +080044import java.util.Set;
Chuck Liao8ec38e02020-02-26 20:59:32 +080045
46/** A {@code ViewGroup} which provides the specific actions for the user to interact with. */
47public class BottomActionBar extends FrameLayout {
48
chihhangchuang3efb6832020-04-17 02:06:25 +080049 /**
50 * Interface to be implemented by an Activity hosting a {@link BottomActionBar}
51 */
52 public interface BottomActionBarHost {
53 /** Gets {@link BottomActionBar}. */
54 BottomActionBar getBottomActionBar();
55 }
56
Chuck Liao829d4d22020-05-07 17:00:10 +080057 /**
58 * The listener for {@link BottomActionBar} visibility change notification.
59 */
60 public interface VisibilityChangeListener {
61 /**
62 * Called when {@link BottomActionBar} visibility changes.
63 *
64 * @param isVisible {@code true} if it's visible; {@code false} otherwise.
65 */
66 void onVisibilityChange(boolean isVisible);
67 }
68
Tracy Zhou16112e32020-05-27 13:13:23 -070069 /** This listens to changes to an action view's selected state. */
70 public interface OnActionSelectedListener {
71
72 /**
73 * This is called when an action view's selected state changes.
74 * @param selected whether the action view is selected.
75 */
76 void onActionSelected(boolean selected);
77 }
78
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +080079 /**
80 * A Callback to notify the registrant to change it's accessibility param when
81 * {@link BottomActionBar} state changes.
82 */
83 public interface AccessibilityCallback {
84 /**
85 * Called when {@link BottomActionBar} collapsed.
86 */
87 void onBottomSheetCollapsed();
88
89 /**
90 * Called when {@link BottomActionBar} expanded.
91 */
92 void onBottomSheetExpanded();
93 }
94
Ching-Sung Li073812b2020-04-07 21:19:21 +080095 // TODO(b/154299462): Separate downloadable related actions from WallpaperPicker.
Chuck Liao8ec38e02020-02-26 20:59:32 +080096 /** The action items in the bottom action bar. */
97 public enum BottomAction {
Chihhang Chuang541e0752020-06-11 19:14:40 +080098 ROTATION, DELETE, INFORMATION, EDIT, CUSTOMIZE, DOWNLOAD, PROGRESS, APPLY
Chuck Liao8ec38e02020-02-26 20:59:32 +080099 }
100
chihhangchuang803ea9a2020-04-21 13:03:10 +0800101 private final Map<BottomAction, View> mActionMap = new EnumMap<>(BottomAction.class);
chihhangchuang1a29e752020-04-28 18:22:53 +0800102 private final Map<BottomAction, View> mContentViewMap = new EnumMap<>(BottomAction.class);
Tracy Zhou16112e32020-05-27 13:13:23 -0700103 private final Map<BottomAction, OnActionSelectedListener> mActionSelectedListeners =
104 new EnumMap<>(BottomAction.class);
chihhangchuang1a29e752020-04-28 18:22:53 +0800105
106 private final ViewGroup mBottomSheetView;
Chihhang Chuang541e0752020-06-11 19:14:40 +0800107 private final QueueStateBottomSheetBehavior<ViewGroup> mBottomSheetBehavior;
Chuck Liao829d4d22020-05-07 17:00:10 +0800108 private final Set<VisibilityChangeListener> mVisibilityChangeListeners = new HashSet<>();
chihhangchuang1a29e752020-04-28 18:22:53 +0800109
Tracy Zhouc36633c2020-05-26 01:38:37 -0700110 // The current selected action in the BottomActionBar, can be null when no action is selected.
chihhangchuang90b45222020-06-17 01:55:29 +0800111 @Nullable private BottomAction mSelectedAction;
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +0800112 @Nullable private AccessibilityCallback mAccessibilityCallback;
Chuck Liao8ec38e02020-02-26 20:59:32 +0800113
114 public BottomActionBar(@NonNull Context context, @Nullable AttributeSet attrs) {
115 super(context, attrs);
116 LayoutInflater.from(context).inflate(R.layout.bottom_actions_layout, this, true);
117
chihhangchuang803ea9a2020-04-21 13:03:10 +0800118 mActionMap.put(BottomAction.ROTATION, findViewById(R.id.action_rotation));
119 mActionMap.put(BottomAction.DELETE, findViewById(R.id.action_delete));
120 mActionMap.put(BottomAction.INFORMATION, findViewById(R.id.action_information));
121 mActionMap.put(BottomAction.EDIT, findViewById(R.id.action_edit));
chihhangchuang08abb582020-04-27 17:20:31 +0800122 mActionMap.put(BottomAction.CUSTOMIZE, findViewById(R.id.action_customize));
chihhangchuang803ea9a2020-04-21 13:03:10 +0800123 mActionMap.put(BottomAction.DOWNLOAD, findViewById(R.id.action_download));
124 mActionMap.put(BottomAction.PROGRESS, findViewById(R.id.action_progress));
125 mActionMap.put(BottomAction.APPLY, findViewById(R.id.action_apply));
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800126
chihhangchuang1a29e752020-04-28 18:22:53 +0800127 mBottomSheetView = findViewById(R.id.action_bottom_sheet);
Santiago Etchebehere53c63432020-05-07 18:55:35 -0700128 SizeCalculator.adjustBackgroundCornerRadius(mBottomSheetView);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800129
Chihhang Chuang541e0752020-06-11 19:14:40 +0800130 mBottomSheetBehavior = (QueueStateBottomSheetBehavior<ViewGroup>) BottomSheetBehavior.from(
131 mBottomSheetView);
chihhangchuang1a29e752020-04-28 18:22:53 +0800132 mBottomSheetBehavior.setState(STATE_COLLAPSED);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800133 mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetCallback() {
134 @Override
135 public void onStateChanged(@NonNull View bottomSheet, int newState) {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800136 if (mBottomSheetBehavior.isQueueProcessing()) {
137 // Avoid button and bottom sheet mismatching from quick tapping buttons when
138 // bottom sheet is changing state.
139 disableActions();
140 // If bottom sheet is going with expanded-collapsed-expanded, the new content
141 // will be updated in collapsed state. The first state change from expanded to
142 // collapsed should still show the previous content view.
143 if (mSelectedAction != null && newState == STATE_COLLAPSED) {
144 updateContentViewFor(mSelectedAction);
145 }
chihhangchuang34ba4122020-05-04 11:25:48 +0800146 return;
147 }
148
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +0800149 notifyAccessibilityCallback(newState);
150
Chihhang Chuang541e0752020-06-11 19:14:40 +0800151 // Enable all buttons when queue is not processing.
152 enableActions();
Chihhang Chuang85f099a2020-06-16 18:04:39 +0800153 if (!isExpandable(mSelectedAction)) {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800154 return;
155 }
Chihhang Chuang85f099a2020-06-16 18:04:39 +0800156 // Ensure the button state is the same as bottom sheet state to catch up the state
157 // change from dragging or some unexpected bottom sheet state changes.
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800158 if (newState == STATE_COLLAPSED) {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800159 updateSelectedState(mSelectedAction, /* selected= */ false);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800160 } else if (newState == STATE_EXPANDED) {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800161 updateSelectedState(mSelectedAction, /* selected= */ true);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800162 }
163 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800164 @Override
Tracy Zhouc36633c2020-05-26 01:38:37 -0700165 public void onSlide(@NonNull View bottomSheet, float slideOffset) { }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800166 });
Chuck Liaoca97c572020-05-08 17:13:25 +0800167
168 setOnApplyWindowInsetsListener((v, windowInsets) -> {
169 v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(),
170 windowInsets.getSystemWindowInsetBottom());
171 return windowInsets;
172 });
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800173 }
174
175 @Override
176 public void onVisibilityAggregated(boolean isVisible) {
177 super.onVisibilityAggregated(isVisible);
178 if (!isVisible) {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800179 hideBottomSheetAndDeselectButtonIfExpanded();
180 mBottomSheetBehavior.reset();
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800181 }
Chuck Liao829d4d22020-05-07 17:00:10 +0800182 mVisibilityChangeListeners.forEach(listener -> listener.onVisibilityChange(isVisible));
Chuck Liao8ec38e02020-02-26 20:59:32 +0800183 }
184
185 /**
Ching-Sung Lie7d5f992020-05-05 22:14:51 +0800186 * Adds content view to the bottom sheet and binds with a {@code BottomAction} to
187 * expand / collapse the bottom sheet.
188 *
189 * @param contentView the view with content to be added on the bottom sheet
190 * @param action the action to be bound to expand / collapse the bottom sheet
191 */
192 public void attachViewToBottomSheetAndBindAction(View contentView, BottomAction action) {
chihhangchuang7bef6562020-05-14 21:19:20 +0800193 contentView.setVisibility(GONE);
chihhangchuangfd5326f2020-06-09 14:14:00 +0800194 contentView.setFocusable(true);
Ching-Sung Lie7d5f992020-05-05 22:14:51 +0800195 mContentViewMap.put(action, contentView);
196 mBottomSheetView.addView(contentView);
chihhangchuangfd5326f2020-06-09 14:14:00 +0800197 setActionClickListener(action, actionView -> {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800198 if (mBottomSheetBehavior.getState() == STATE_COLLAPSED) {
199 updateContentViewFor(action);
200 }
chihhangchuangfd5326f2020-06-09 14:14:00 +0800201 mBottomSheetView.setAccessibilityTraversalAfter(actionView.getId());
Ching-Sung Lie7d5f992020-05-05 22:14:51 +0800202 });
203 }
204
Chihhang Chuang541e0752020-06-11 19:14:40 +0800205 /** Collapses the bottom sheet. */
206 public void collapseBottomSheetIfExpanded() {
207 hideBottomSheetAndDeselectButtonIfExpanded();
Ching-Sung Lib9404622020-05-18 22:38:45 +0800208 }
209
210 /**
Tracy Zhou16112e32020-05-27 13:13:23 -0700211 * Sets a click listener to a specific action.
Chuck Liao8ec38e02020-02-26 20:59:32 +0800212 *
213 * @param bottomAction the specific action
214 * @param actionClickListener the click listener for the action
215 */
216 public void setActionClickListener(
217 BottomAction bottomAction, OnClickListener actionClickListener) {
chihhangchuang1a29e752020-04-28 18:22:53 +0800218 View buttonView = mActionMap.get(bottomAction);
219 if (buttonView.hasOnClickListeners()) {
220 throw new IllegalStateException(
221 "Had already set a click listener to button: " + bottomAction);
222 }
Tracy Zhou67fd7e32020-05-01 18:35:36 -0700223 buttonView.setOnClickListener(view -> {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800224 if (mSelectedAction != null && isActionSelected(mSelectedAction)) {
Tracy Zhouc36633c2020-05-26 01:38:37 -0700225 updateSelectedState(mSelectedAction, /* selected= */ false);
Chihhang Chuang541e0752020-06-11 19:14:40 +0800226 if (isExpandable(mSelectedAction)) {
227 mBottomSheetBehavior.enqueue(STATE_COLLAPSED);
Tracy Zhouc36633c2020-05-26 01:38:37 -0700228 }
Chihhang Chuang541e0752020-06-11 19:14:40 +0800229 } else {
230 // Error handling, set to null if the action is not selected.
231 mSelectedAction = null;
Tracy Zhouc36633c2020-05-26 01:38:37 -0700232 }
233
234 if (bottomAction == mSelectedAction) {
235 // Deselect the selected action.
236 mSelectedAction = null;
237 } else {
238 // Select a different action from the current selected action.
239 mSelectedAction = bottomAction;
240 updateSelectedState(mSelectedAction, /* selected= */ true);
Chihhang Chuang541e0752020-06-11 19:14:40 +0800241 if (isExpandable(mSelectedAction)) {
242 mBottomSheetBehavior.enqueue(STATE_EXPANDED);
Tracy Zhouc36633c2020-05-26 01:38:37 -0700243 }
244 }
Tracy Zhou67fd7e32020-05-01 18:35:36 -0700245 actionClickListener.onClick(view);
Chihhang Chuang541e0752020-06-11 19:14:40 +0800246 mBottomSheetBehavior.processQueueForStateChange();
Tracy Zhou67fd7e32020-05-01 18:35:36 -0700247 });
Chuck Liao8ec38e02020-02-26 20:59:32 +0800248 }
249
Tracy Zhou16112e32020-05-27 13:13:23 -0700250 /**
251 * Sets a selected listener to a specific action. This is triggered each time the bottom
252 * action's selected state changes.
253 *
254 * @param bottomAction the specific action
255 * @param actionSelectedListener the selected listener for the action
256 */
257 public void setActionSelectedListener(
258 BottomAction bottomAction, OnActionSelectedListener actionSelectedListener) {
259 if (mActionSelectedListeners.containsKey(bottomAction)) {
260 throw new IllegalStateException(
261 "Had already set a selected listener to button: " + bottomAction);
262 }
263 mActionSelectedListeners.put(bottomAction, actionSelectedListener);
264 }
265
Kunhung Lic6701492021-01-28 17:35:56 +0800266 /** Set back button visibility. */
267 public void setBackButtonVisibility(int visibility) {
268 findViewById(R.id.action_back).setVisibility(visibility);
269 }
270
271 /** Get back button visibility. */
272 public int getBackButtonVisibility() {
273 return findViewById(R.id.action_back).getVisibility();
274 }
275
chihhangchuang7feb3752020-04-24 02:48:56 +0800276 /** Binds the cancel button to back key. */
chihhangchuang08abb582020-04-27 17:20:31 +0800277 public void bindBackButtonToSystemBackKey(Activity activity) {
278 findViewById(R.id.action_back).setOnClickListener(v -> activity.onBackPressed());
chihhangchuang7feb3752020-04-24 02:48:56 +0800279 }
280
chihhangchuang2aa79fa2020-04-15 19:29:30 +0800281 /** Returns {@code true} if visible. */
282 public boolean isVisible() {
283 return getVisibility() == VISIBLE;
284 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800285
Chuck Liao4f059312020-03-13 23:09:46 +0800286 /** Shows {@link BottomActionBar}. */
287 public void show() {
288 setVisibility(VISIBLE);
289 }
290
291 /** Hides {@link BottomActionBar}. */
292 public void hide() {
293 setVisibility(GONE);
294 }
295
Chuck Liao8ec38e02020-02-26 20:59:32 +0800296 /**
Chuck Liao829d4d22020-05-07 17:00:10 +0800297 * Adds the visibility change listener.
298 *
299 * @param visibilityChangeListener the listener to be notified.
300 */
301 public void addVisibilityChangeListener(VisibilityChangeListener visibilityChangeListener) {
302 if (visibilityChangeListener == null) {
303 return;
304 }
305 mVisibilityChangeListeners.add(visibilityChangeListener);
306 visibilityChangeListener.onVisibilityChange(isVisible());
307 }
308
309 /**
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +0800310 * Sets a AccessibilityCallback.
311 *
312 * @param accessibilityCallback the callback to be notified.
313 */
314 public void setAccessibilityCallback(@Nullable AccessibilityCallback accessibilityCallback) {
315 mAccessibilityCallback = accessibilityCallback;
316 }
317
318 /**
Chuck Liao8ec38e02020-02-26 20:59:32 +0800319 * Shows the specific actions.
320 *
321 * @param actions the specific actions
322 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800323 public void showActions(BottomAction... actions) {
324 for (BottomAction action : actions) {
325 mActionMap.get(action).setVisibility(VISIBLE);
326 }
Chuck Liao8ec38e02020-02-26 20:59:32 +0800327 }
328
329 /**
330 * Hides the specific actions.
331 *
332 * @param actions the specific actions
333 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800334 public void hideActions(BottomAction... actions) {
335 for (BottomAction action : actions) {
336 mActionMap.get(action).setVisibility(GONE);
337
Chihhang Chuang541e0752020-06-11 19:14:40 +0800338 if (isExpandable(action) && mSelectedAction == action) {
339 hideBottomSheetAndDeselectButtonIfExpanded();
chihhangchuang803ea9a2020-04-21 13:03:10 +0800340 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800341 }
Chuck Liao8ec38e02020-02-26 20:59:32 +0800342 }
343
344 /**
345 * Shows the specific actions only. In other words, the other actions will be hidden.
346 *
347 * @param actions the specific actions which will be shown. Others will be hidden.
348 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800349 public void showActionsOnly(BottomAction... actions) {
350 final Set<BottomAction> actionsSet = new HashSet<>(Arrays.asList(actions));
351
chihhangchuang90b45222020-06-17 01:55:29 +0800352 mActionMap.keySet().forEach(action -> {
chihhangchuang803ea9a2020-04-21 13:03:10 +0800353 if (actionsSet.contains(action)) {
354 showActions(action);
355 } else {
356 hideActions(action);
357 }
358 });
359 }
360
361 /**
Chuck Liao58e4a1c2020-05-22 11:35:35 +0800362 * Checks if the specific actions are shown.
363 *
364 * @param actions the specific actions to be verified
365 * @return {@code true} if the actions are shown; {@code false} otherwise
366 */
367 public boolean areActionsShown(BottomAction... actions) {
368 final Set<BottomAction> actionsSet = new HashSet<>(Arrays.asList(actions));
369 return actionsSet.stream().allMatch(bottomAction -> {
370 View view = mActionMap.get(bottomAction);
371 return view != null && view.getVisibility() == VISIBLE;
372 });
373 }
374
375 /**
chihhangchuang803ea9a2020-04-21 13:03:10 +0800376 * All actions will be hidden.
377 */
378 public void hideAllActions() {
379 showActionsOnly(/* No actions to show */);
Chuck Liao8ec38e02020-02-26 20:59:32 +0800380 }
381
Chuck Liao69630f12020-03-05 19:01:25 +0800382 /** Enables all the actions' {@link View}. */
383 public void enableActions() {
chihhangchuang1a29e752020-04-28 18:22:53 +0800384 enableActions(BottomAction.values());
Chuck Liao69630f12020-03-05 19:01:25 +0800385 }
386
387 /** Disables all the actions' {@link View}. */
388 public void disableActions() {
chihhangchuang1a29e752020-04-28 18:22:53 +0800389 disableActions(BottomAction.values());
Chuck Liao69630f12020-03-05 19:01:25 +0800390 }
391
Ching-Sung Li073812b2020-04-07 21:19:21 +0800392 /**
393 * Enables specified actions' {@link View}.
394 *
395 * @param actions the specified actions to enable their views
396 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800397 public void enableActions(BottomAction... actions) {
398 for (BottomAction action : actions) {
399 mActionMap.get(action).setEnabled(true);
400 }
Ching-Sung Li073812b2020-04-07 21:19:21 +0800401 }
402
403 /**
404 * Disables specified actions' {@link View}.
405 *
406 * @param actions the specified actions to disable their views
407 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800408 public void disableActions(BottomAction... actions) {
409 for (BottomAction action : actions) {
410 mActionMap.get(action).setEnabled(false);
411 }
Ching-Sung Li073812b2020-04-07 21:19:21 +0800412 }
413
Chihhang Chuangca7c8252020-06-17 17:32:46 +0800414 /** Sets a default selected action button. */
415 public void setDefaultSelectedButton(BottomAction action) {
416 if (mSelectedAction == null) {
417 mSelectedAction = action;
418 updateSelectedState(mSelectedAction, /* selected= */ true);
419 }
420 }
421
422 /** Deselects an action button. */
423 public void deselectAction(BottomAction action) {
424 if (isExpandable(action)) {
425 mBottomSheetBehavior.setState(STATE_COLLAPSED);
426 }
427 updateSelectedState(action, /* selected= */ false);
428 if (action == mSelectedAction) {
429 mSelectedAction = null;
430 }
Tracy Zhoue4a5f262020-05-26 17:40:19 -0700431 }
432
Tracy Zhou67fd7e32020-05-01 18:35:36 -0700433 public boolean isActionSelected(BottomAction action) {
434 return mActionMap.get(action).isSelected();
435 }
436
chihhangchuang90b45222020-06-17 01:55:29 +0800437 /** Resets {@link BottomActionBar} to initial state. */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800438 public void reset() {
chihhangchuang90b45222020-06-17 01:55:29 +0800439 // Not visible by default, see res/layout/bottom_action_bar.xml
chihhangchuang803ea9a2020-04-21 13:03:10 +0800440 hide();
chihhangchuang90b45222020-06-17 01:55:29 +0800441 // All actions are hide and enabled by default, see res/layout/bottom_action_bar.xml
chihhangchuang803ea9a2020-04-21 13:03:10 +0800442 hideAllActions();
chihhangchuang803ea9a2020-04-21 13:03:10 +0800443 enableActions();
chihhangchuang90b45222020-06-17 01:55:29 +0800444 // Clears all the actions' click listeners
445 mActionMap.values().forEach(v -> v.setOnClickListener(null));
446 findViewById(R.id.action_back).setOnClickListener(null);
447 // Deselect all buttons.
Chihhang Chuang541e0752020-06-11 19:14:40 +0800448 mActionMap.keySet().forEach(a -> updateSelectedState(a, /* selected= */ false));
chihhangchuang90b45222020-06-17 01:55:29 +0800449 // Clear values.
chihhangchuang1a29e752020-04-28 18:22:53 +0800450 mContentViewMap.clear();
chihhangchuang90b45222020-06-17 01:55:29 +0800451 mActionSelectedListeners.clear();
452 mBottomSheetView.removeAllViews();
Chihhang Chuang541e0752020-06-11 19:14:40 +0800453 mBottomSheetBehavior.reset();
454 mSelectedAction = null;
Chuck Liao8ec38e02020-02-26 20:59:32 +0800455 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800456
Tracy Zhou16112e32020-05-27 13:13:23 -0700457 private void updateSelectedState(BottomAction bottomAction, boolean selected) {
458 View bottomActionView = mActionMap.get(bottomAction);
459 if (bottomActionView.isSelected() == selected) {
460 return;
461 }
462
463 OnActionSelectedListener listener = mActionSelectedListeners.get(bottomAction);
464 if (listener != null) {
465 listener.onActionSelected(selected);
466 }
467 bottomActionView.setSelected(selected);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800468 }
Chihhang Chuang541e0752020-06-11 19:14:40 +0800469
470 private void hideBottomSheetAndDeselectButtonIfExpanded() {
471 if (isExpandable(mSelectedAction) && mBottomSheetBehavior.getState() == STATE_EXPANDED) {
472 mBottomSheetBehavior.setState(STATE_COLLAPSED);
473 updateSelectedState(mSelectedAction, /* selected= */ false);
474 mSelectedAction = null;
475 }
476 }
477
478 private void updateContentViewFor(BottomAction action) {
479 mContentViewMap.forEach((a, v) -> v.setVisibility(a.equals(action) ? VISIBLE : GONE));
480 }
481
482 private boolean isExpandable(BottomAction action) {
483 return action != null && mContentViewMap.containsKey(action);
484 }
485
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +0800486 private void notifyAccessibilityCallback(int state) {
chihhangchuangaf7e49d2020-06-24 22:46:04 +0800487 if (mAccessibilityCallback == null) {
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +0800488 return;
489 }
490
491 if (state == STATE_COLLAPSED) {
492 mAccessibilityCallback.onBottomSheetCollapsed();
493 } else if (state == STATE_EXPANDED) {
494 mAccessibilityCallback.onBottomSheetExpanded();
495 }
496 }
497
Chihhang Chuang541e0752020-06-11 19:14:40 +0800498 /** A {@link BottomSheetBehavior} that can process a queue of bottom sheet states.*/
499 public static class QueueStateBottomSheetBehavior<V extends View>
500 extends BottomSheetBehavior<V> {
501
502 private final Deque<Integer> mStateQueue = new ArrayDeque<>();
503 private boolean mIsQueueProcessing;
504
505 public QueueStateBottomSheetBehavior(Context context, @Nullable AttributeSet attrs) {
506 super(context, attrs);
507 // Binds the default callback for processing queue.
508 setBottomSheetCallback(null);
509 }
510
511 /** Enqueues the bottom sheet states. */
512 public void enqueue(int state) {
513 if (!mStateQueue.isEmpty() && mStateQueue.getLast() == state) {
514 return;
515 }
516 mStateQueue.add(state);
517 }
518
519 /** Processes the queue of bottom sheet state that was set via {@link #enqueue}. */
520 public void processQueueForStateChange() {
521 if (mStateQueue.isEmpty()) {
522 return;
523 }
524 setState(mStateQueue.getFirst());
525 mIsQueueProcessing = true;
526 }
527
528 /**
529 * Returns {@code true} if the queue is processing. For example, if the bottom sheet is
530 * going with expanded-collapsed-expanded, it would return {@code true} until last expanded
Chihhang Chuang85f099a2020-06-16 18:04:39 +0800531 * state is finished.
532 */
Chihhang Chuang541e0752020-06-11 19:14:40 +0800533 public boolean isQueueProcessing() {
534 return mIsQueueProcessing;
535 }
536
537 /** Resets the queue state. */
538 public void reset() {
539 mStateQueue.clear();
540 mIsQueueProcessing = false;
541 }
542
543 @Override
544 public void setBottomSheetCallback(BottomSheetCallback callback) {
545 super.setBottomSheetCallback(new BottomSheetCallback() {
546 @Override
547 public void onStateChanged(@NonNull View bottomSheet, int newState) {
548 if (!mStateQueue.isEmpty()) {
549 if (newState == mStateQueue.getFirst()) {
550 mStateQueue.removeFirst();
551 if (mStateQueue.isEmpty()) {
552 mIsQueueProcessing = false;
553 } else {
554 setState(mStateQueue.getFirst());
555 }
556 } else {
557 setState(mStateQueue.getFirst());
558 }
559 }
560
561 if (callback != null) {
562 callback.onStateChanged(bottomSheet, newState);
563 }
564 }
565
566 @Override
567 public void onSlide(@NonNull View bottomSheet, float slideOffset) {
568 if (callback != null) {
569 callback.onSlide(bottomSheet, slideOffset);
570 }
571 }
572 });
573 }
574 }
Chuck Liao8ec38e02020-02-26 20:59:32 +0800575}