blob: fda4c8b5540e26fbe544b721f75d7caa3f268bf7 [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 Chuang1ec44b72021-06-14 14:49:25 +080098 ROTATION, DELETE, INFORMATION, EDIT, CUSTOMIZE, DOWNLOAD, PROGRESS, APPLY, APPLY_TEXT
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));
Chihhang Chuang1ec44b72021-06-14 14:49:25 +0800126 mActionMap.put(BottomAction.APPLY_TEXT, findViewById(R.id.action_apply_text_button));
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800127
chihhangchuang1a29e752020-04-28 18:22:53 +0800128 mBottomSheetView = findViewById(R.id.action_bottom_sheet);
Santiago Etchebehere53c63432020-05-07 18:55:35 -0700129 SizeCalculator.adjustBackgroundCornerRadius(mBottomSheetView);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800130
Chihhang Chuang541e0752020-06-11 19:14:40 +0800131 mBottomSheetBehavior = (QueueStateBottomSheetBehavior<ViewGroup>) BottomSheetBehavior.from(
132 mBottomSheetView);
chihhangchuang1a29e752020-04-28 18:22:53 +0800133 mBottomSheetBehavior.setState(STATE_COLLAPSED);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800134 mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetCallback() {
135 @Override
136 public void onStateChanged(@NonNull View bottomSheet, int newState) {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800137 if (mBottomSheetBehavior.isQueueProcessing()) {
138 // Avoid button and bottom sheet mismatching from quick tapping buttons when
139 // bottom sheet is changing state.
140 disableActions();
141 // If bottom sheet is going with expanded-collapsed-expanded, the new content
142 // will be updated in collapsed state. The first state change from expanded to
143 // collapsed should still show the previous content view.
144 if (mSelectedAction != null && newState == STATE_COLLAPSED) {
145 updateContentViewFor(mSelectedAction);
146 }
chihhangchuang34ba4122020-05-04 11:25:48 +0800147 return;
148 }
149
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +0800150 notifyAccessibilityCallback(newState);
151
Chihhang Chuang541e0752020-06-11 19:14:40 +0800152 // Enable all buttons when queue is not processing.
153 enableActions();
Chihhang Chuang85f099a2020-06-16 18:04:39 +0800154 if (!isExpandable(mSelectedAction)) {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800155 return;
156 }
Chihhang Chuang85f099a2020-06-16 18:04:39 +0800157 // Ensure the button state is the same as bottom sheet state to catch up the state
158 // change from dragging or some unexpected bottom sheet state changes.
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800159 if (newState == STATE_COLLAPSED) {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800160 updateSelectedState(mSelectedAction, /* selected= */ false);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800161 } else if (newState == STATE_EXPANDED) {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800162 updateSelectedState(mSelectedAction, /* selected= */ true);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800163 }
164 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800165 @Override
Tracy Zhouc36633c2020-05-26 01:38:37 -0700166 public void onSlide(@NonNull View bottomSheet, float slideOffset) { }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800167 });
Chuck Liaoca97c572020-05-08 17:13:25 +0800168
169 setOnApplyWindowInsetsListener((v, windowInsets) -> {
170 v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(),
171 windowInsets.getSystemWindowInsetBottom());
172 return windowInsets;
173 });
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800174 }
175
176 @Override
177 public void onVisibilityAggregated(boolean isVisible) {
178 super.onVisibilityAggregated(isVisible);
Chuck Liao829d4d22020-05-07 17:00:10 +0800179 mVisibilityChangeListeners.forEach(listener -> listener.onVisibilityChange(isVisible));
Chuck Liao8ec38e02020-02-26 20:59:32 +0800180 }
181
182 /**
Ching-Sung Lie7d5f992020-05-05 22:14:51 +0800183 * Adds content view to the bottom sheet and binds with a {@code BottomAction} to
184 * expand / collapse the bottom sheet.
185 *
186 * @param contentView the view with content to be added on the bottom sheet
187 * @param action the action to be bound to expand / collapse the bottom sheet
188 */
189 public void attachViewToBottomSheetAndBindAction(View contentView, BottomAction action) {
chihhangchuang7bef6562020-05-14 21:19:20 +0800190 contentView.setVisibility(GONE);
chihhangchuangfd5326f2020-06-09 14:14:00 +0800191 contentView.setFocusable(true);
Ching-Sung Lie7d5f992020-05-05 22:14:51 +0800192 mContentViewMap.put(action, contentView);
193 mBottomSheetView.addView(contentView);
chihhangchuangfd5326f2020-06-09 14:14:00 +0800194 setActionClickListener(action, actionView -> {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800195 if (mBottomSheetBehavior.getState() == STATE_COLLAPSED) {
196 updateContentViewFor(action);
197 }
chihhangchuangfd5326f2020-06-09 14:14:00 +0800198 mBottomSheetView.setAccessibilityTraversalAfter(actionView.getId());
Ching-Sung Lie7d5f992020-05-05 22:14:51 +0800199 });
200 }
201
Chihhang Chuang541e0752020-06-11 19:14:40 +0800202 /** Collapses the bottom sheet. */
203 public void collapseBottomSheetIfExpanded() {
204 hideBottomSheetAndDeselectButtonIfExpanded();
Ching-Sung Lib9404622020-05-18 22:38:45 +0800205 }
206
Chihhang Chuang1e8bece2021-05-17 15:08:06 +0800207 /** Enables or disables action buttons that show the bottom sheet. */
208 public void enableActionButtonsWithBottomSheet(boolean enabled) {
209 if (enabled) {
210 enableActions(mContentViewMap.keySet().toArray(new BottomAction[0]));
211 } else {
212 disableActions(mContentViewMap.keySet().toArray(new BottomAction[0]));
213 }
214 }
215
Ching-Sung Lib9404622020-05-18 22:38:45 +0800216 /**
Tracy Zhou16112e32020-05-27 13:13:23 -0700217 * Sets a click listener to a specific action.
Chuck Liao8ec38e02020-02-26 20:59:32 +0800218 *
219 * @param bottomAction the specific action
220 * @param actionClickListener the click listener for the action
221 */
222 public void setActionClickListener(
223 BottomAction bottomAction, OnClickListener actionClickListener) {
chihhangchuang1a29e752020-04-28 18:22:53 +0800224 View buttonView = mActionMap.get(bottomAction);
225 if (buttonView.hasOnClickListeners()) {
226 throw new IllegalStateException(
227 "Had already set a click listener to button: " + bottomAction);
228 }
Tracy Zhou67fd7e32020-05-01 18:35:36 -0700229 buttonView.setOnClickListener(view -> {
Chihhang Chuang541e0752020-06-11 19:14:40 +0800230 if (mSelectedAction != null && isActionSelected(mSelectedAction)) {
Tracy Zhouc36633c2020-05-26 01:38:37 -0700231 updateSelectedState(mSelectedAction, /* selected= */ false);
Chihhang Chuang541e0752020-06-11 19:14:40 +0800232 if (isExpandable(mSelectedAction)) {
233 mBottomSheetBehavior.enqueue(STATE_COLLAPSED);
Tracy Zhouc36633c2020-05-26 01:38:37 -0700234 }
Chihhang Chuang541e0752020-06-11 19:14:40 +0800235 } else {
236 // Error handling, set to null if the action is not selected.
237 mSelectedAction = null;
Tracy Zhouc36633c2020-05-26 01:38:37 -0700238 }
239
240 if (bottomAction == mSelectedAction) {
241 // Deselect the selected action.
242 mSelectedAction = null;
243 } else {
244 // Select a different action from the current selected action.
245 mSelectedAction = bottomAction;
246 updateSelectedState(mSelectedAction, /* selected= */ true);
Chihhang Chuang541e0752020-06-11 19:14:40 +0800247 if (isExpandable(mSelectedAction)) {
248 mBottomSheetBehavior.enqueue(STATE_EXPANDED);
Tracy Zhouc36633c2020-05-26 01:38:37 -0700249 }
250 }
Tracy Zhou67fd7e32020-05-01 18:35:36 -0700251 actionClickListener.onClick(view);
Chihhang Chuang541e0752020-06-11 19:14:40 +0800252 mBottomSheetBehavior.processQueueForStateChange();
Tracy Zhou67fd7e32020-05-01 18:35:36 -0700253 });
Chuck Liao8ec38e02020-02-26 20:59:32 +0800254 }
255
Tracy Zhou16112e32020-05-27 13:13:23 -0700256 /**
257 * Sets a selected listener to a specific action. This is triggered each time the bottom
258 * action's selected state changes.
259 *
260 * @param bottomAction the specific action
261 * @param actionSelectedListener the selected listener for the action
262 */
263 public void setActionSelectedListener(
264 BottomAction bottomAction, OnActionSelectedListener actionSelectedListener) {
265 if (mActionSelectedListeners.containsKey(bottomAction)) {
266 throw new IllegalStateException(
267 "Had already set a selected listener to button: " + bottomAction);
268 }
269 mActionSelectedListeners.put(bottomAction, actionSelectedListener);
270 }
271
Kunhung Lic6701492021-01-28 17:35:56 +0800272 /** Set back button visibility. */
273 public void setBackButtonVisibility(int visibility) {
274 findViewById(R.id.action_back).setVisibility(visibility);
275 }
276
chihhangchuang7feb3752020-04-24 02:48:56 +0800277 /** Binds the cancel button to back key. */
chihhangchuang08abb582020-04-27 17:20:31 +0800278 public void bindBackButtonToSystemBackKey(Activity activity) {
279 findViewById(R.id.action_back).setOnClickListener(v -> activity.onBackPressed());
chihhangchuang7feb3752020-04-24 02:48:56 +0800280 }
281
chihhangchuang2aa79fa2020-04-15 19:29:30 +0800282 /** Returns {@code true} if visible. */
283 public boolean isVisible() {
284 return getVisibility() == VISIBLE;
285 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800286
Chuck Liao4f059312020-03-13 23:09:46 +0800287 /** Shows {@link BottomActionBar}. */
288 public void show() {
289 setVisibility(VISIBLE);
290 }
291
292 /** Hides {@link BottomActionBar}. */
293 public void hide() {
294 setVisibility(GONE);
295 }
296
Chuck Liao8ec38e02020-02-26 20:59:32 +0800297 /**
Chuck Liao829d4d22020-05-07 17:00:10 +0800298 * Adds the visibility change listener.
299 *
300 * @param visibilityChangeListener the listener to be notified.
301 */
302 public void addVisibilityChangeListener(VisibilityChangeListener visibilityChangeListener) {
303 if (visibilityChangeListener == null) {
304 return;
305 }
306 mVisibilityChangeListeners.add(visibilityChangeListener);
307 visibilityChangeListener.onVisibilityChange(isVisible());
308 }
309
310 /**
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +0800311 * Sets a AccessibilityCallback.
312 *
313 * @param accessibilityCallback the callback to be notified.
314 */
315 public void setAccessibilityCallback(@Nullable AccessibilityCallback accessibilityCallback) {
316 mAccessibilityCallback = accessibilityCallback;
317 }
318
319 /**
Chuck Liao8ec38e02020-02-26 20:59:32 +0800320 * Shows the specific actions.
321 *
322 * @param actions the specific actions
323 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800324 public void showActions(BottomAction... actions) {
325 for (BottomAction action : actions) {
326 mActionMap.get(action).setVisibility(VISIBLE);
327 }
Chuck Liao8ec38e02020-02-26 20:59:32 +0800328 }
329
330 /**
331 * Hides the specific actions.
332 *
333 * @param actions the specific actions
334 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800335 public void hideActions(BottomAction... actions) {
336 for (BottomAction action : actions) {
337 mActionMap.get(action).setVisibility(GONE);
338
Chihhang Chuang541e0752020-06-11 19:14:40 +0800339 if (isExpandable(action) && mSelectedAction == action) {
340 hideBottomSheetAndDeselectButtonIfExpanded();
chihhangchuang803ea9a2020-04-21 13:03:10 +0800341 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800342 }
Chuck Liao8ec38e02020-02-26 20:59:32 +0800343 }
344
345 /**
346 * Shows the specific actions only. In other words, the other actions will be hidden.
347 *
348 * @param actions the specific actions which will be shown. Others will be hidden.
349 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800350 public void showActionsOnly(BottomAction... actions) {
351 final Set<BottomAction> actionsSet = new HashSet<>(Arrays.asList(actions));
352
chihhangchuang90b45222020-06-17 01:55:29 +0800353 mActionMap.keySet().forEach(action -> {
chihhangchuang803ea9a2020-04-21 13:03:10 +0800354 if (actionsSet.contains(action)) {
355 showActions(action);
356 } else {
357 hideActions(action);
358 }
359 });
360 }
361
362 /**
Chuck Liao58e4a1c2020-05-22 11:35:35 +0800363 * Checks if the specific actions are shown.
364 *
365 * @param actions the specific actions to be verified
366 * @return {@code true} if the actions are shown; {@code false} otherwise
367 */
368 public boolean areActionsShown(BottomAction... actions) {
369 final Set<BottomAction> actionsSet = new HashSet<>(Arrays.asList(actions));
370 return actionsSet.stream().allMatch(bottomAction -> {
371 View view = mActionMap.get(bottomAction);
372 return view != null && view.getVisibility() == VISIBLE;
373 });
374 }
375
376 /**
chihhangchuang803ea9a2020-04-21 13:03:10 +0800377 * All actions will be hidden.
378 */
379 public void hideAllActions() {
380 showActionsOnly(/* No actions to show */);
Chuck Liao8ec38e02020-02-26 20:59:32 +0800381 }
382
Chuck Liao69630f12020-03-05 19:01:25 +0800383 /** Enables all the actions' {@link View}. */
384 public void enableActions() {
chihhangchuang1a29e752020-04-28 18:22:53 +0800385 enableActions(BottomAction.values());
Chuck Liao69630f12020-03-05 19:01:25 +0800386 }
387
388 /** Disables all the actions' {@link View}. */
389 public void disableActions() {
chihhangchuang1a29e752020-04-28 18:22:53 +0800390 disableActions(BottomAction.values());
Chuck Liao69630f12020-03-05 19:01:25 +0800391 }
392
Ching-Sung Li073812b2020-04-07 21:19:21 +0800393 /**
394 * Enables specified actions' {@link View}.
395 *
396 * @param actions the specified actions to enable their views
397 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800398 public void enableActions(BottomAction... actions) {
399 for (BottomAction action : actions) {
400 mActionMap.get(action).setEnabled(true);
401 }
Ching-Sung Li073812b2020-04-07 21:19:21 +0800402 }
403
404 /**
405 * Disables specified actions' {@link View}.
406 *
407 * @param actions the specified actions to disable their views
408 */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800409 public void disableActions(BottomAction... actions) {
410 for (BottomAction action : actions) {
411 mActionMap.get(action).setEnabled(false);
412 }
Ching-Sung Li073812b2020-04-07 21:19:21 +0800413 }
414
Chihhang Chuangca7c8252020-06-17 17:32:46 +0800415 /** Sets a default selected action button. */
416 public void setDefaultSelectedButton(BottomAction action) {
417 if (mSelectedAction == null) {
418 mSelectedAction = action;
419 updateSelectedState(mSelectedAction, /* selected= */ true);
420 }
421 }
422
423 /** Deselects an action button. */
424 public void deselectAction(BottomAction action) {
425 if (isExpandable(action)) {
426 mBottomSheetBehavior.setState(STATE_COLLAPSED);
427 }
428 updateSelectedState(action, /* selected= */ false);
429 if (action == mSelectedAction) {
430 mSelectedAction = null;
431 }
Tracy Zhoue4a5f262020-05-26 17:40:19 -0700432 }
433
Tracy Zhou67fd7e32020-05-01 18:35:36 -0700434 public boolean isActionSelected(BottomAction action) {
435 return mActionMap.get(action).isSelected();
436 }
437
Chihhang Chuang1e8bece2021-05-17 15:08:06 +0800438 /** Returns {@code true} if the state of bottom sheet is collapsed. */
439 public boolean isBottomSheetCollapsed() {
440 return mBottomSheetBehavior.getState() == STATE_COLLAPSED;
441 }
442
chihhangchuang90b45222020-06-17 01:55:29 +0800443 /** Resets {@link BottomActionBar} to initial state. */
chihhangchuang803ea9a2020-04-21 13:03:10 +0800444 public void reset() {
chihhangchuang90b45222020-06-17 01:55:29 +0800445 // Not visible by default, see res/layout/bottom_action_bar.xml
chihhangchuang803ea9a2020-04-21 13:03:10 +0800446 hide();
chihhangchuang90b45222020-06-17 01:55:29 +0800447 // All actions are hide and enabled by default, see res/layout/bottom_action_bar.xml
chihhangchuang803ea9a2020-04-21 13:03:10 +0800448 hideAllActions();
chihhangchuang803ea9a2020-04-21 13:03:10 +0800449 enableActions();
chihhangchuang90b45222020-06-17 01:55:29 +0800450 // Clears all the actions' click listeners
451 mActionMap.values().forEach(v -> v.setOnClickListener(null));
452 findViewById(R.id.action_back).setOnClickListener(null);
453 // Deselect all buttons.
Chihhang Chuang541e0752020-06-11 19:14:40 +0800454 mActionMap.keySet().forEach(a -> updateSelectedState(a, /* selected= */ false));
chihhangchuang90b45222020-06-17 01:55:29 +0800455 // Clear values.
chihhangchuang1a29e752020-04-28 18:22:53 +0800456 mContentViewMap.clear();
chihhangchuang90b45222020-06-17 01:55:29 +0800457 mActionSelectedListeners.clear();
458 mBottomSheetView.removeAllViews();
Chihhang Chuang541e0752020-06-11 19:14:40 +0800459 mBottomSheetBehavior.reset();
460 mSelectedAction = null;
Chuck Liao8ec38e02020-02-26 20:59:32 +0800461 }
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800462
Tracy Zhou16112e32020-05-27 13:13:23 -0700463 private void updateSelectedState(BottomAction bottomAction, boolean selected) {
464 View bottomActionView = mActionMap.get(bottomAction);
465 if (bottomActionView.isSelected() == selected) {
466 return;
467 }
468
469 OnActionSelectedListener listener = mActionSelectedListeners.get(bottomAction);
470 if (listener != null) {
471 listener.onActionSelected(selected);
472 }
473 bottomActionView.setSelected(selected);
chihhangchuang22aa0cc2020-03-25 19:12:42 +0800474 }
Chihhang Chuang541e0752020-06-11 19:14:40 +0800475
476 private void hideBottomSheetAndDeselectButtonIfExpanded() {
477 if (isExpandable(mSelectedAction) && mBottomSheetBehavior.getState() == STATE_EXPANDED) {
478 mBottomSheetBehavior.setState(STATE_COLLAPSED);
479 updateSelectedState(mSelectedAction, /* selected= */ false);
480 mSelectedAction = null;
481 }
482 }
483
484 private void updateContentViewFor(BottomAction action) {
485 mContentViewMap.forEach((a, v) -> v.setVisibility(a.equals(action) ? VISIBLE : GONE));
486 }
487
488 private boolean isExpandable(BottomAction action) {
489 return action != null && mContentViewMap.containsKey(action);
490 }
491
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +0800492 private void notifyAccessibilityCallback(int state) {
chihhangchuangaf7e49d2020-06-24 22:46:04 +0800493 if (mAccessibilityCallback == null) {
Wesley.CW Wang61c28ec2020-06-19 18:58:50 +0800494 return;
495 }
496
497 if (state == STATE_COLLAPSED) {
498 mAccessibilityCallback.onBottomSheetCollapsed();
499 } else if (state == STATE_EXPANDED) {
500 mAccessibilityCallback.onBottomSheetExpanded();
501 }
502 }
503
Chihhang Chuang541e0752020-06-11 19:14:40 +0800504 /** A {@link BottomSheetBehavior} that can process a queue of bottom sheet states.*/
505 public static class QueueStateBottomSheetBehavior<V extends View>
506 extends BottomSheetBehavior<V> {
507
508 private final Deque<Integer> mStateQueue = new ArrayDeque<>();
509 private boolean mIsQueueProcessing;
510
511 public QueueStateBottomSheetBehavior(Context context, @Nullable AttributeSet attrs) {
512 super(context, attrs);
513 // Binds the default callback for processing queue.
514 setBottomSheetCallback(null);
515 }
516
517 /** Enqueues the bottom sheet states. */
518 public void enqueue(int state) {
519 if (!mStateQueue.isEmpty() && mStateQueue.getLast() == state) {
520 return;
521 }
522 mStateQueue.add(state);
523 }
524
525 /** Processes the queue of bottom sheet state that was set via {@link #enqueue}. */
526 public void processQueueForStateChange() {
527 if (mStateQueue.isEmpty()) {
528 return;
529 }
530 setState(mStateQueue.getFirst());
531 mIsQueueProcessing = true;
532 }
533
534 /**
535 * Returns {@code true} if the queue is processing. For example, if the bottom sheet is
536 * going with expanded-collapsed-expanded, it would return {@code true} until last expanded
Chihhang Chuang85f099a2020-06-16 18:04:39 +0800537 * state is finished.
538 */
Chihhang Chuang541e0752020-06-11 19:14:40 +0800539 public boolean isQueueProcessing() {
540 return mIsQueueProcessing;
541 }
542
543 /** Resets the queue state. */
544 public void reset() {
545 mStateQueue.clear();
546 mIsQueueProcessing = false;
547 }
548
549 @Override
550 public void setBottomSheetCallback(BottomSheetCallback callback) {
551 super.setBottomSheetCallback(new BottomSheetCallback() {
552 @Override
553 public void onStateChanged(@NonNull View bottomSheet, int newState) {
554 if (!mStateQueue.isEmpty()) {
555 if (newState == mStateQueue.getFirst()) {
556 mStateQueue.removeFirst();
557 if (mStateQueue.isEmpty()) {
558 mIsQueueProcessing = false;
559 } else {
560 setState(mStateQueue.getFirst());
561 }
562 } else {
563 setState(mStateQueue.getFirst());
564 }
565 }
566
567 if (callback != null) {
568 callback.onStateChanged(bottomSheet, newState);
569 }
570 }
571
572 @Override
573 public void onSlide(@NonNull View bottomSheet, float slideOffset) {
574 if (callback != null) {
575 callback.onSlide(bottomSheet, slideOffset);
576 }
577 }
578 });
579 }
580 }
Chuck Liao8ec38e02020-02-26 20:59:32 +0800581}