blob: 3a895251789af8f38fb8e00c6b20beda37192fc5 [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
Chihhang Chuang1e8bece2021-05-17 15:08:06 +0800210 /** Enables or disables action buttons that show the bottom sheet. */
211 public void enableActionButtonsWithBottomSheet(boolean enabled) {
212 if (enabled) {
213 enableActions(mContentViewMap.keySet().toArray(new BottomAction[0]));
214 } else {
215 disableActions(mContentViewMap.keySet().toArray(new BottomAction[0]));
216 }
217 }
218
Ching-Sung Lib9404622020-05-18 22:38:45 +0800219 /**
Tracy Zhou16112e32020-05-27 13:13:23 -0700220 * Sets a click listener to a specific action.
Chuck Liao8ec38e02020-02-26 20:59:32 +0800221 *
222 * @param bottomAction the specific action
223 * @param actionClickListener the click listener for the action
224 */
225 public void setActionClickListener(
226 BottomAction bottomAction, OnClickListener actionClickListener) {
chihhangchuang1a29e752020-04-28 18:22:53 +0800227 View buttonView = mActionMap.get(bottomAction);
228 if (buttonView.hasOnClickListeners()) {
229 throw new IllegalStateException(
230 "Had already set a click listener to button: " + bottomAction);
231 }
Tracy Zhou67fd7e32020-05-01 18:35:36 -0700232 buttonView.setOnClickListener(view -> {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800233 if (mSelectedAction != null && isActionSelected(mSelectedAction)) {
Tracy Zhouc36633c2020-05-26 01:38:37 -0700234 updateSelectedState(mSelectedAction, /* selected= */ false);
Chihhang Chuang541e0752020-06-11 19:14:40 +0800235 if (isExpandable(mSelectedAction)) {
236 mBottomSheetBehavior.enqueue(STATE_COLLAPSED);
Tracy Zhouc36633c2020-05-26 01:38:37 -0700237 }
Chihhang Chuang541e0752020-06-11 19:14:40 +0800238 } else {
239 // Error handling, set to null if the action is not selected.
240 mSelectedAction = null;
Tracy Zhouc36633c2020-05-26 01:38:37 -0700241 }
242
243 if (bottomAction == mSelectedAction) {
244 // Deselect the selected action.
245 mSelectedAction = null;
246 } else {
247 // Select a different action from the current selected action.
248 mSelectedAction = bottomAction;
249 updateSelectedState(mSelectedAction, /* selected= */ true);
Chihhang Chuang541e0752020-06-11 19:14:40 +0800250 if (isExpandable(mSelectedAction)) {
251 mBottomSheetBehavior.enqueue(STATE_EXPANDED);
Tracy Zhouc36633c2020-05-26 01:38:37 -0700252 }
253 }
Tracy Zhou67fd7e32020-05-01 18:35:36 -0700254 actionClickListener.onClick(view);
Chihhang Chuang541e0752020-06-11 19:14:40 +0800255 mBottomSheetBehavior.processQueueForStateChange();
Tracy Zhou67fd7e32020-05-01 18:35:36 -0700256 });
Chuck Liao8ec38e02020-02-26 20:59:32 +0800257 }
258
Tracy Zhou16112e32020-05-27 13:13:23 -0700259 /**
260 * Sets a selected listener to a specific action. This is triggered each time the bottom
261 * action's selected state changes.
262 *
263 * @param bottomAction the specific action
264 * @param actionSelectedListener the selected listener for the action
265 */
266 public void setActionSelectedListener(
267 BottomAction bottomAction, OnActionSelectedListener actionSelectedListener) {
268 if (mActionSelectedListeners.containsKey(bottomAction)) {
269 throw new IllegalStateException(
270 "Had already set a selected listener to button: " + bottomAction);
271 }
272 mActionSelectedListeners.put(bottomAction, actionSelectedListener);
273 }
274
Kunhung Lic6701492021-01-28 17:35:56 +0800275 /** Set back button visibility. */
276 public void setBackButtonVisibility(int visibility) {
277 findViewById(R.id.action_back).setVisibility(visibility);
278 }
279
280 /** Get back button visibility. */
281 public int getBackButtonVisibility() {
282 return findViewById(R.id.action_back).getVisibility();
283 }
284
chihhangchuang7feb3752020-04-24 02:48:56 +0800285 /** Binds the cancel button to back key. */
chihhangchuang08abb582020-04-27 17:20:31 +0800286 public void bindBackButtonToSystemBackKey(Activity activity) {
287 findViewById(R.id.action_back).setOnClickListener(v -> activity.onBackPressed());
chihhangchuang7feb3752020-04-24 02:48:56 +0800288 }
289
chihhangchuang2aa79fa2020-04-15 19:29:30 +0800290 /** Returns {@code true} if visible. */
291 public boolean isVisible() {
292 return getVisibility() == VISIBLE;
293 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800294
Chuck Liao4f059312020-03-13 23:09:46 +0800295 /** Shows {@link BottomActionBar}. */
296 public void show() {
297 setVisibility(VISIBLE);
298 }
299
300 /** Hides {@link BottomActionBar}. */
301 public void hide() {
302 setVisibility(GONE);
303 }
304
Chuck Liao8ec38e02020-02-26 20:59:32 +0800305 /**
Chuck Liao829d4d22020-05-07 17:00:10 +0800306 * Adds the visibility change listener.
307 *
308 * @param visibilityChangeListener the listener to be notified.
309 */
310 public void addVisibilityChangeListener(VisibilityChangeListener visibilityChangeListener) {
311 if (visibilityChangeListener == null) {
312 return;
313 }
314 mVisibilityChangeListeners.add(visibilityChangeListener);
315 visibilityChangeListener.onVisibilityChange(isVisible());
316 }
317
318 /**
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +0800319 * Sets a AccessibilityCallback.
320 *
321 * @param accessibilityCallback the callback to be notified.
322 */
323 public void setAccessibilityCallback(@Nullable AccessibilityCallback accessibilityCallback) {
324 mAccessibilityCallback = accessibilityCallback;
325 }
326
327 /**
Chuck Liao8ec38e02020-02-26 20:59:32 +0800328 * Shows the specific actions.
329 *
330 * @param actions the specific actions
331 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800332 public void showActions(BottomAction... actions) {
333 for (BottomAction action : actions) {
334 mActionMap.get(action).setVisibility(VISIBLE);
335 }
Chuck Liao8ec38e02020-02-26 20:59:32 +0800336 }
337
338 /**
339 * Hides the specific actions.
340 *
341 * @param actions the specific actions
342 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800343 public void hideActions(BottomAction... actions) {
344 for (BottomAction action : actions) {
345 mActionMap.get(action).setVisibility(GONE);
346
Chihhang Chuang541e0752020-06-11 19:14:40 +0800347 if (isExpandable(action) && mSelectedAction == action) {
348 hideBottomSheetAndDeselectButtonIfExpanded();
chihhangchuang803ea9a2020-04-21 13:03:10 +0800349 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800350 }
Chuck Liao8ec38e02020-02-26 20:59:32 +0800351 }
352
353 /**
354 * Shows the specific actions only. In other words, the other actions will be hidden.
355 *
356 * @param actions the specific actions which will be shown. Others will be hidden.
357 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800358 public void showActionsOnly(BottomAction... actions) {
359 final Set<BottomAction> actionsSet = new HashSet<>(Arrays.asList(actions));
360
chihhangchuang90b45222020-06-17 01:55:29 +0800361 mActionMap.keySet().forEach(action -> {
chihhangchuang803ea9a2020-04-21 13:03:10 +0800362 if (actionsSet.contains(action)) {
363 showActions(action);
364 } else {
365 hideActions(action);
366 }
367 });
368 }
369
370 /**
Chuck Liao58e4a1c2020-05-22 11:35:35 +0800371 * Checks if the specific actions are shown.
372 *
373 * @param actions the specific actions to be verified
374 * @return {@code true} if the actions are shown; {@code false} otherwise
375 */
376 public boolean areActionsShown(BottomAction... actions) {
377 final Set<BottomAction> actionsSet = new HashSet<>(Arrays.asList(actions));
378 return actionsSet.stream().allMatch(bottomAction -> {
379 View view = mActionMap.get(bottomAction);
380 return view != null && view.getVisibility() == VISIBLE;
381 });
382 }
383
384 /**
chihhangchuang803ea9a2020-04-21 13:03:10 +0800385 * All actions will be hidden.
386 */
387 public void hideAllActions() {
388 showActionsOnly(/* No actions to show */);
Chuck Liao8ec38e02020-02-26 20:59:32 +0800389 }
390
Chuck Liao69630f12020-03-05 19:01:25 +0800391 /** Enables all the actions' {@link View}. */
392 public void enableActions() {
chihhangchuang1a29e752020-04-28 18:22:53 +0800393 enableActions(BottomAction.values());
Chuck Liao69630f12020-03-05 19:01:25 +0800394 }
395
396 /** Disables all the actions' {@link View}. */
397 public void disableActions() {
chihhangchuang1a29e752020-04-28 18:22:53 +0800398 disableActions(BottomAction.values());
Chuck Liao69630f12020-03-05 19:01:25 +0800399 }
400
Ching-Sung Li073812b2020-04-07 21:19:21 +0800401 /**
402 * Enables specified actions' {@link View}.
403 *
404 * @param actions the specified actions to enable their views
405 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800406 public void enableActions(BottomAction... actions) {
407 for (BottomAction action : actions) {
408 mActionMap.get(action).setEnabled(true);
409 }
Ching-Sung Li073812b2020-04-07 21:19:21 +0800410 }
411
412 /**
413 * Disables specified actions' {@link View}.
414 *
415 * @param actions the specified actions to disable their views
416 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800417 public void disableActions(BottomAction... actions) {
418 for (BottomAction action : actions) {
419 mActionMap.get(action).setEnabled(false);
420 }
Ching-Sung Li073812b2020-04-07 21:19:21 +0800421 }
422
Chihhang Chuangca7c8252020-06-17 17:32:46 +0800423 /** Sets a default selected action button. */
424 public void setDefaultSelectedButton(BottomAction action) {
425 if (mSelectedAction == null) {
426 mSelectedAction = action;
427 updateSelectedState(mSelectedAction, /* selected= */ true);
428 }
429 }
430
431 /** Deselects an action button. */
432 public void deselectAction(BottomAction action) {
433 if (isExpandable(action)) {
434 mBottomSheetBehavior.setState(STATE_COLLAPSED);
435 }
436 updateSelectedState(action, /* selected= */ false);
437 if (action == mSelectedAction) {
438 mSelectedAction = null;
439 }
Tracy Zhoue4a5f262020-05-26 17:40:19 -0700440 }
441
Tracy Zhou67fd7e32020-05-01 18:35:36 -0700442 public boolean isActionSelected(BottomAction action) {
443 return mActionMap.get(action).isSelected();
444 }
445
Chihhang Chuang1e8bece2021-05-17 15:08:06 +0800446 /** Returns {@code true} if the state of bottom sheet is collapsed. */
447 public boolean isBottomSheetCollapsed() {
448 return mBottomSheetBehavior.getState() == STATE_COLLAPSED;
449 }
450
chihhangchuang90b45222020-06-17 01:55:29 +0800451 /** Resets {@link BottomActionBar} to initial state. */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800452 public void reset() {
chihhangchuang90b45222020-06-17 01:55:29 +0800453 // Not visible by default, see res/layout/bottom_action_bar.xml
chihhangchuang803ea9a2020-04-21 13:03:10 +0800454 hide();
chihhangchuang90b45222020-06-17 01:55:29 +0800455 // All actions are hide and enabled by default, see res/layout/bottom_action_bar.xml
chihhangchuang803ea9a2020-04-21 13:03:10 +0800456 hideAllActions();
chihhangchuang803ea9a2020-04-21 13:03:10 +0800457 enableActions();
chihhangchuang90b45222020-06-17 01:55:29 +0800458 // Clears all the actions' click listeners
459 mActionMap.values().forEach(v -> v.setOnClickListener(null));
460 findViewById(R.id.action_back).setOnClickListener(null);
461 // Deselect all buttons.
Chihhang Chuang541e0752020-06-11 19:14:40 +0800462 mActionMap.keySet().forEach(a -> updateSelectedState(a, /* selected= */ false));
chihhangchuang90b45222020-06-17 01:55:29 +0800463 // Clear values.
chihhangchuang1a29e752020-04-28 18:22:53 +0800464 mContentViewMap.clear();
chihhangchuang90b45222020-06-17 01:55:29 +0800465 mActionSelectedListeners.clear();
466 mBottomSheetView.removeAllViews();
Chihhang Chuang541e0752020-06-11 19:14:40 +0800467 mBottomSheetBehavior.reset();
468 mSelectedAction = null;
Chuck Liao8ec38e02020-02-26 20:59:32 +0800469 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800470
Tracy Zhou16112e32020-05-27 13:13:23 -0700471 private void updateSelectedState(BottomAction bottomAction, boolean selected) {
472 View bottomActionView = mActionMap.get(bottomAction);
473 if (bottomActionView.isSelected() == selected) {
474 return;
475 }
476
477 OnActionSelectedListener listener = mActionSelectedListeners.get(bottomAction);
478 if (listener != null) {
479 listener.onActionSelected(selected);
480 }
481 bottomActionView.setSelected(selected);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800482 }
Chihhang Chuang541e0752020-06-11 19:14:40 +0800483
484 private void hideBottomSheetAndDeselectButtonIfExpanded() {
485 if (isExpandable(mSelectedAction) && mBottomSheetBehavior.getState() == STATE_EXPANDED) {
486 mBottomSheetBehavior.setState(STATE_COLLAPSED);
487 updateSelectedState(mSelectedAction, /* selected= */ false);
488 mSelectedAction = null;
489 }
490 }
491
492 private void updateContentViewFor(BottomAction action) {
493 mContentViewMap.forEach((a, v) -> v.setVisibility(a.equals(action) ? VISIBLE : GONE));
494 }
495
496 private boolean isExpandable(BottomAction action) {
497 return action != null && mContentViewMap.containsKey(action);
498 }
499
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +0800500 private void notifyAccessibilityCallback(int state) {
chihhangchuangaf7e49d2020-06-24 22:46:04 +0800501 if (mAccessibilityCallback == null) {
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +0800502 return;
503 }
504
505 if (state == STATE_COLLAPSED) {
506 mAccessibilityCallback.onBottomSheetCollapsed();
507 } else if (state == STATE_EXPANDED) {
508 mAccessibilityCallback.onBottomSheetExpanded();
509 }
510 }
511
Chihhang Chuang541e0752020-06-11 19:14:40 +0800512 /** A {@link BottomSheetBehavior} that can process a queue of bottom sheet states.*/
513 public static class QueueStateBottomSheetBehavior<V extends View>
514 extends BottomSheetBehavior<V> {
515
516 private final Deque<Integer> mStateQueue = new ArrayDeque<>();
517 private boolean mIsQueueProcessing;
518
519 public QueueStateBottomSheetBehavior(Context context, @Nullable AttributeSet attrs) {
520 super(context, attrs);
521 // Binds the default callback for processing queue.
522 setBottomSheetCallback(null);
523 }
524
525 /** Enqueues the bottom sheet states. */
526 public void enqueue(int state) {
527 if (!mStateQueue.isEmpty() && mStateQueue.getLast() == state) {
528 return;
529 }
530 mStateQueue.add(state);
531 }
532
533 /** Processes the queue of bottom sheet state that was set via {@link #enqueue}. */
534 public void processQueueForStateChange() {
535 if (mStateQueue.isEmpty()) {
536 return;
537 }
538 setState(mStateQueue.getFirst());
539 mIsQueueProcessing = true;
540 }
541
542 /**
543 * Returns {@code true} if the queue is processing. For example, if the bottom sheet is
544 * going with expanded-collapsed-expanded, it would return {@code true} until last expanded
Chihhang Chuang85f099a2020-06-16 18:04:39 +0800545 * state is finished.
546 */
Chihhang Chuang541e0752020-06-11 19:14:40 +0800547 public boolean isQueueProcessing() {
548 return mIsQueueProcessing;
549 }
550
551 /** Resets the queue state. */
552 public void reset() {
553 mStateQueue.clear();
554 mIsQueueProcessing = false;
555 }
556
557 @Override
558 public void setBottomSheetCallback(BottomSheetCallback callback) {
559 super.setBottomSheetCallback(new BottomSheetCallback() {
560 @Override
561 public void onStateChanged(@NonNull View bottomSheet, int newState) {
562 if (!mStateQueue.isEmpty()) {
563 if (newState == mStateQueue.getFirst()) {
564 mStateQueue.removeFirst();
565 if (mStateQueue.isEmpty()) {
566 mIsQueueProcessing = false;
567 } else {
568 setState(mStateQueue.getFirst());
569 }
570 } else {
571 setState(mStateQueue.getFirst());
572 }
573 }
574
575 if (callback != null) {
576 callback.onStateChanged(bottomSheet, newState);
577 }
578 }
579
580 @Override
581 public void onSlide(@NonNull View bottomSheet, float slideOffset) {
582 if (callback != null) {
583 callback.onSlide(bottomSheet, slideOffset);
584 }
585 }
586 });
587 }
588 }
Chuck Liao8ec38e02020-02-26 20:59:32 +0800589}