chromium/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/MoreProgressButton.java

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.browser_ui.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.chromium.ui.widget.ButtonCompat;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * A more button that will automatically turn to progress spinner and run an associated action when
 * clicked.
 *
 * To use, add {@link R.layout.more_progress_button} to your view hierarchy and set a click listener
 * via {@link #setOnClickRunnable()}. Initially the MoreProgressButton starts in
 * {@link State#Hidden}.
 *
 * Call {@link #setState(int)} to transition between the loading spinner, button, or hidden states.
 */
public class MoreProgressButton extends FrameLayout implements View.OnClickListener {
    /** State for the button, reflects the visibility for the button and spinner */
    @IntDef({State.INVALID, State.HIDDEN, State.BUTTON, State.LOADING})
    @Retention(RetentionPolicy.SOURCE)
    public @interface State {
        /** Internal state used before the button finished inflating. */
        int INVALID = -1;

        /** Both the button and spinner are GONE. */
        int HIDDEN = 0;

        /** The button is visible and the loading spinner is hidden. */
        int BUTTON = 1;

        /** The spinner is visible and the button is hidden. */
        int LOADING = 2;
    }

    protected View mProgressSpinner;
    protected ButtonCompat mButton;
    protected Runnable mOnClickRunnable;

    protected @State int mState = State.INVALID;

    public MoreProgressButton(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void onFinishInflate() {
        super.onFinishInflate();

        mButton = findViewById(R.id.action_button);
        mButton.setOnClickListener(this);

        mProgressSpinner = findViewById(R.id.progress_spinner);

        setState(State.HIDDEN);
    }

    @Override
    public void onClick(View view) {
        assert view == mButton;

        setState(State.LOADING);
        if (mOnClickRunnable != null) mOnClickRunnable.run();
    }

    /**
     * Set the runnable that the button will execute when the button is clicked.
     * @param onClickRunnable Runnable that will run in {@link #onClick(View)}
     */
    public void setOnClickRunnable(Runnable onClickRunnable) {
        mOnClickRunnable = onClickRunnable;
    }

    /**
     * Set the state for the more button.
     * @param state New state for the button. Must be one of {@link State#HIDDEN},
     *              {@link State#BUTTON}, or {@link State#LOADING}.
     */
    public void setState(@State int state) {
        if (state == mState) return;

        assert state != State.INVALID;

        mState = state;
        this.mButton.setVisibility(State.BUTTON == state ? View.VISIBLE : View.GONE);
        this.mProgressSpinner.setVisibility(State.LOADING == state ? View.VISIBLE : View.GONE);
    }

    public void setButtonText(String text) {
        mButton.setText(text);
    }

    public @State int getStateForTest() {
        return mState;
    }
}