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