1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.components.browser_ui.widget;
6 
7 import android.content.Context;
8 import android.util.AttributeSet;
9 import android.view.View;
10 import android.widget.FrameLayout;
11 
12 import androidx.annotation.IntDef;
13 import androidx.annotation.NonNull;
14 import androidx.annotation.Nullable;
15 import androidx.annotation.VisibleForTesting;
16 
17 import java.lang.annotation.Retention;
18 import java.lang.annotation.RetentionPolicy;
19 
20 /**
21  * A more button that will automatically turn to progress spinner and run an associated action when
22  * clicked.
23  *
24  * To use, add {@link R.layout.more_progress_button} to your view hierarchy and set a click listener
25  * via {@link #setOnClickRunnable()}. Initially the MoreProgressButton starts in
26  * {@link State#Hidden}.
27  *
28  * Call {@link #setState(int)} to transition between the loading spinner, button, or hidden states.
29  */
30 public class MoreProgressButton extends FrameLayout implements View.OnClickListener {
31     /**
32      * State for the button, reflects the visibility for the button and spinner
33      */
34     @IntDef({State.INVALID, State.HIDDEN, State.BUTTON, State.LOADING})
35     @Retention(RetentionPolicy.SOURCE)
36     public @interface State {
37         /**
38          * Internal state used before the button finished inflating.
39          */
40         int INVALID = -1;
41 
42         /**
43          * Both the button and spinner are GONE.
44          */
45         int HIDDEN = 0;
46 
47         /**
48          * The button is visible and the loading spinner is hidden.
49          */
50         int BUTTON = 1;
51 
52         /**
53          * The spinner is visible and the button is hidden.
54          */
55         int LOADING = 2;
56     }
57 
58     protected View mProgressSpinner;
59     protected View mButton;
60     protected Runnable mOnClickRunnable;
61 
62     protected @State int mState = State.INVALID;
63 
MoreProgressButton(@onNull Context context, @Nullable AttributeSet attrs)64     public MoreProgressButton(@NonNull Context context, @Nullable AttributeSet attrs) {
65         super(context, attrs);
66     }
67 
68     @Override
onFinishInflate()69     public void onFinishInflate() {
70         super.onFinishInflate();
71 
72         mButton = findViewById(R.id.action_button);
73         mButton.setOnClickListener(this);
74 
75         mProgressSpinner = findViewById(R.id.progress_spinner);
76 
77         setState(State.HIDDEN);
78     }
79 
80     @Override
onClick(View view)81     public void onClick(View view) {
82         assert view == mButton;
83 
84         setState(State.LOADING);
85         if (mOnClickRunnable != null) mOnClickRunnable.run();
86     }
87 
88     /**
89      * Set the runnable that the button will execute when the button is clicked.
90      * @param onClickRunnable Runnable that will run in {@link #onClick(View)}
91      */
setOnClickRunnable(Runnable onClickRunnable)92     public void setOnClickRunnable(Runnable onClickRunnable) {
93         mOnClickRunnable = onClickRunnable;
94     }
95 
96     /**
97      * Set the state for the more button.
98      * @param state New state for the button. Must be one of {@link State#HIDDEN},
99      *              {@link State#BUTTON}, or {@link State#LOADING}.
100      */
setState(@tate int state)101     public void setState(@State int state) {
102         if (state == mState) return;
103 
104         assert state != State.INVALID;
105 
106         mState = state;
107         this.mButton.setVisibility(State.BUTTON == state ? View.VISIBLE : View.GONE);
108         this.mProgressSpinner.setVisibility(State.LOADING == state ? View.VISIBLE : View.GONE);
109     }
110 
111     @VisibleForTesting
getStateForTest()112     public @State int getStateForTest() {
113         return mState;
114     }
115 }
116