375 lines
13 KiB
Java
375 lines
13 KiB
Java
/*
|
|
* Copyright 2017 Martijn Brekhof. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package org.xbmc.kore.ui.widgets.fabspeeddial;
|
|
|
|
import android.annotation.TargetApi;
|
|
import android.content.Context;
|
|
import android.content.res.ColorStateList;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.Bundle;
|
|
import android.os.Parcelable;
|
|
import android.preference.PreferenceManager;
|
|
import android.support.annotation.Nullable;
|
|
import android.support.design.widget.FloatingActionButton;
|
|
import android.support.v7.content.res.AppCompatResources;
|
|
import android.util.AttributeSet;
|
|
import android.util.TypedValue;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.animation.AccelerateInterpolator;
|
|
import android.view.animation.OvershootInterpolator;
|
|
import android.widget.LinearLayout;
|
|
|
|
import org.xbmc.kore.R;
|
|
import org.xbmc.kore.Settings;
|
|
import org.xbmc.kore.ui.animators.ChangeImageFadeAnimation;
|
|
import org.xbmc.kore.ui.animators.PulsateAnimation;
|
|
|
|
import butterknife.ButterKnife;
|
|
import butterknife.BindView;
|
|
import butterknife.Unbinder;
|
|
|
|
/**
|
|
* The Floating Action Button Speed Dial uses a {@link FloatingActionButton} and can
|
|
* optionally show a speed dial menu. To enable the speed dials add a listener
|
|
* for the dials using {@link #setOnDialItemClickListener(DialListener)}.
|
|
*
|
|
* <p>The icons for the FAB needs to be set through your theme:
|
|
* <ul>
|
|
* <li>org.xbmc.kore.R.attr.iconFABDefault sets the icon when the dials are disabled</li>
|
|
* <li>org.xbmc.kore.R.attr.iconFABDialsOpenClose sets the icon when the dials are enabled</li>
|
|
* </ul>
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* The background color can be set through your theme:
|
|
* <ul>
|
|
* <li>org.xbmc.kore.R.attr.fabColorNormal sets the default color</li>
|
|
* <li>org.xbmc.kore.R.attr.fabColorPressed sets the pressed state color</li>
|
|
* <li>org.xbmc.kore.R.attr.fabColorFocus sets the focus state color</li>
|
|
* </ul>
|
|
* </p>
|
|
*/
|
|
public class FABSpeedDial extends LinearLayout {
|
|
@BindView(R.id.fabspeeddial) FloatingActionButton FABMain;
|
|
@BindView(R.id.play_local) DialActionButton FABPlayLocal;
|
|
@BindView(R.id.play_remote) DialActionButton FABPlayRemote;
|
|
|
|
private final String BUNDLE_KEY_EXPANDED = "expanded";
|
|
private final String BUNDLE_KEY_PARENT = "parent";
|
|
private final String BUNDLE_KEY_DIALCLICKED = "dialclicked";
|
|
|
|
private PulsateAnimation busyAnimation;
|
|
private DialActionButton dialSelected;
|
|
private boolean dialsVisible;
|
|
private boolean dialsEnabled;
|
|
|
|
private Drawable iconFABDefault;
|
|
private Drawable iconFABOpenClose;
|
|
|
|
private OvershootInterpolator showDialsInterpolator = new OvershootInterpolator();
|
|
private AccelerateInterpolator hideDialsInterpolator = new AccelerateInterpolator();
|
|
|
|
private Unbinder unbinder;
|
|
|
|
public interface DialListener {
|
|
void onLocalPlayClicked();
|
|
void onRemotePlayClicked();
|
|
}
|
|
|
|
private DialListener dialListener;
|
|
private OnClickListener fabListener;
|
|
|
|
public FABSpeedDial(Context context) {
|
|
this(context, null);
|
|
}
|
|
|
|
public FABSpeedDial(Context context, @Nullable AttributeSet attrs) {
|
|
this(context, attrs, 0);
|
|
}
|
|
|
|
public FABSpeedDial(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
|
super(context, attrs, defStyleAttr);
|
|
initializeView(context);
|
|
}
|
|
|
|
@TargetApi(21)
|
|
public FABSpeedDial(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
super(context, attrs, defStyleAttr, defStyleRes);
|
|
initializeView(context);
|
|
}
|
|
|
|
@Override
|
|
protected void onDetachedFromWindow() {
|
|
super.onDetachedFromWindow();
|
|
unbinder.unbind();
|
|
|
|
dialListener = null;
|
|
fabListener = null;
|
|
}
|
|
|
|
/**
|
|
* Enables/disables the speed dials. This means that if enabled,
|
|
* the dials will be shown if the user pressed the FAB button.
|
|
* @param enable true to enable the dials, false to disable
|
|
* @param animate true to use animation to change FAB icon, false to instantly change the FAB icon
|
|
*/
|
|
public void enableSpeedDials(boolean enable, boolean animate) {
|
|
if (dialsEnabled == enable)
|
|
return;
|
|
|
|
dialsEnabled = enable;
|
|
|
|
changeFABIcon(animate);
|
|
}
|
|
|
|
/**
|
|
* Add listener to handle dial button click events.
|
|
* <br/>
|
|
* Note: adding a listener for the dials also enables the speed dials if
|
|
* user didn't disable usage in settings
|
|
* @param dialListener
|
|
*/
|
|
public void setOnDialItemClickListener(DialListener dialListener) {
|
|
this.dialListener = dialListener;
|
|
|
|
// Disable speed dials if user disabled it through settings
|
|
boolean disable = PreferenceManager
|
|
.getDefaultSharedPreferences(getContext())
|
|
.getBoolean(Settings.KEY_PREF_DISABLE_LOCAL_PLAY,
|
|
Settings.DEFAULT_PREF_DISABLE_LOCAL_PLAY);
|
|
|
|
enableSpeedDials(!disable, false);
|
|
}
|
|
|
|
/**
|
|
* Add listener to handle FAB click events.
|
|
* <br/>
|
|
* Note: if the speed dials are enabled this won't be called
|
|
* when the FAB button is pressed.
|
|
* @param fabListener
|
|
*/
|
|
public void setOnFabClickListener(OnClickListener fabListener) {
|
|
this.fabListener = fabListener;
|
|
}
|
|
|
|
/**
|
|
* WARNING: Do not use this to set a listener for the FAB button.
|
|
* Use {@link #setOnFabClickListener(OnClickListener)}
|
|
* instead.
|
|
* <br/>
|
|
* {@inheritDoc}
|
|
* @param l
|
|
*/
|
|
@Override
|
|
public void setOnClickListener(@Nullable OnClickListener l) {
|
|
super.setOnClickListener(l);
|
|
}
|
|
|
|
/**
|
|
* Enables/disables the FAB button and starts/stops the busy animation
|
|
* @param enable true to disable the FAB button and start the busy animation, false to enable
|
|
* the FAB button and stop the busy animation.
|
|
*/
|
|
public void enableBusyAnimation(boolean enable) {
|
|
if (enable) {
|
|
busyAnimation.start();
|
|
if (dialSelected != null) {
|
|
changeFABIcon(FABMain.getDrawable(), dialSelected.getDrawable());
|
|
}
|
|
FABMain.setEnabled(false);
|
|
} else {
|
|
busyAnimation.stop();
|
|
if (dialSelected != null) {
|
|
changeFABIcon(true);
|
|
dialSelected = null;
|
|
}
|
|
FABMain.setEnabled(true);
|
|
}
|
|
}
|
|
|
|
public boolean busyAnimationIsEnabled() {
|
|
return busyAnimation.isRunning();
|
|
}
|
|
|
|
public void enableLocalPlay(boolean enable) {
|
|
FABPlayLocal.setEnabled(enable);
|
|
}
|
|
|
|
public void showDials(boolean show) {
|
|
dialsVisible = show;
|
|
|
|
if (show) {
|
|
FABMain.animate().setInterpolator(showDialsInterpolator);
|
|
FABMain.animate().rotation(-45f);
|
|
FABPlayLocal.show();
|
|
FABPlayRemote.show();
|
|
} else {
|
|
FABMain.animate().setInterpolator(hideDialsInterpolator);
|
|
FABMain.animate().rotation(0f);
|
|
FABPlayLocal.hide();
|
|
FABPlayRemote.hide();
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
protected Parcelable onSaveInstanceState() {
|
|
Bundle bundle = new Bundle();
|
|
bundle.putParcelable(BUNDLE_KEY_PARENT, super.onSaveInstanceState());
|
|
bundle.putBoolean(BUNDLE_KEY_EXPANDED, dialsVisible);
|
|
if (dialSelected != null) {
|
|
bundle.putCharSequence(BUNDLE_KEY_DIALCLICKED, dialSelected.getLabel().getText());
|
|
}
|
|
return bundle;
|
|
}
|
|
|
|
@Override
|
|
protected void onRestoreInstanceState(Parcelable state) {
|
|
if (state != null) {
|
|
Bundle bundle = (Bundle) state;
|
|
|
|
super.onRestoreInstanceState(bundle.getParcelable(BUNDLE_KEY_PARENT));
|
|
showDials(bundle.getBoolean(BUNDLE_KEY_EXPANDED));
|
|
|
|
CharSequence charSequence = bundle.getCharSequence(BUNDLE_KEY_DIALCLICKED);
|
|
if ((charSequence != null) && (! charSequence.equals(FABPlayLocal.getLabel().getText()))) {
|
|
dialSelected = FABPlayRemote;
|
|
|
|
enableBusyAnimation(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void initializeView(Context context) {
|
|
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
|
View view = inflater.inflate(R.layout.fab_speed_dial, this);
|
|
|
|
unbinder = ButterKnife.bind(view);
|
|
|
|
// Makes sure shadow is not clipped
|
|
setClipToPadding(false);
|
|
|
|
// Makes sure translation animations do not cause clipping
|
|
// by parent view group when moving outside its boundaries.
|
|
// For example, when using the overshoot interpolator.
|
|
setClipChildren(false);
|
|
|
|
setupListeners();
|
|
|
|
setupFABIcon(context);
|
|
setupDial(FABPlayLocal);
|
|
setupDial(FABPlayRemote);
|
|
}
|
|
|
|
private void setupFABIcon(Context context) {
|
|
TypedValue tv = new TypedValue();
|
|
|
|
context.getTheme().resolveAttribute(R.attr.iconFABDialsOpenClose, tv, false);
|
|
iconFABOpenClose = AppCompatResources.getDrawable(context, tv.data);
|
|
context.getTheme().resolveAttribute(R.attr.iconFABDefault, tv, false);
|
|
iconFABDefault = AppCompatResources.getDrawable(context, tv.data);
|
|
|
|
FABMain.setImageDrawable(dialsEnabled ? iconFABOpenClose : iconFABDefault);
|
|
|
|
ColorStateList colorStateList = AppCompatResources.getColorStateList(context, R.color.fabspeeddial);
|
|
int fabColorNormal = colorStateList.getColorForState(new int[] {android.R.attr.state_enabled},
|
|
R.attr.colorPrimaryDark);
|
|
int fabColorPressed = colorStateList.getColorForState(new int[] {android.R.attr.state_pressed},
|
|
R.attr.colorPrimary);
|
|
|
|
busyAnimation = new PulsateAnimation(FABMain, fabColorNormal, fabColorPressed);
|
|
|
|
FABMain.setBackgroundTintList(colorStateList);
|
|
}
|
|
|
|
private void setupDial(DialActionButton dialActionButton) {
|
|
dialActionButton.setAnchorView(FABMain);
|
|
dialActionButton.setShowInterpolator(showDialsInterpolator);
|
|
dialActionButton.setHideInterpolator(hideDialsInterpolator);
|
|
}
|
|
|
|
private void setupListeners() {
|
|
FABMain.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
if (dialsEnabled) {
|
|
showDials(!FABPlayLocal.isShown());
|
|
} else {
|
|
if (fabListener != null) {
|
|
fabListener.onClick(v);
|
|
} else if (dialListener != null) {
|
|
/**
|
|
* We take remote play as default and we try to fallback if dev misconfigured
|
|
* the FAB in {@link org.xbmc.kore.ui.AbstractInfoFragment#setupFAB(FABSpeedDial)}.
|
|
* This is also needed to support disabling local playback through settings.
|
|
*/
|
|
dialListener.onRemotePlayClicked();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
FABPlayLocal.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
dialSelected = FABPlayLocal;
|
|
if (dialListener != null) {
|
|
dialListener.onLocalPlayClicked();
|
|
showDials(false);
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
FABPlayRemote.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
dialSelected = FABPlayRemote;
|
|
if (dialListener != null) {
|
|
dialListener.onRemotePlayClicked();
|
|
showDials(false);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private ChangeImageFadeAnimation changeImageFadeAnimation;
|
|
|
|
private void changeFABIcon(final Drawable from, final Drawable to) {
|
|
// Cancel previous animation if any
|
|
if (changeImageFadeAnimation != null)
|
|
changeImageFadeAnimation.cancel();
|
|
|
|
changeImageFadeAnimation = new ChangeImageFadeAnimation(FABMain, from, to);
|
|
changeImageFadeAnimation.start();
|
|
}
|
|
|
|
/**
|
|
* Changes the FAB icon to its default value.
|
|
* @param animate true to use an animation to change the icon, false to change it instantly
|
|
*/
|
|
private void changeFABIcon(boolean animate) {
|
|
Drawable drawable = dialsEnabled ? iconFABOpenClose : iconFABDefault;
|
|
|
|
if (animate) {
|
|
changeFABIcon(FABMain.getDrawable(), drawable);
|
|
} else {
|
|
FABMain.setImageDrawable(drawable);
|
|
}
|
|
}
|
|
}
|