@@ -35,7 +35,6 @@ import com.wireguard.android.databinding.TunnelListFragmentBinding; | |||
import com.wireguard.android.databinding.TunnelListItemBinding; | |||
import com.wireguard.android.model.Tunnel; | |||
import com.wireguard.android.util.ExceptionLoggers; | |||
import com.wireguard.android.widget.ToggleSwitch; | |||
import com.wireguard.config.Config; | |||
import java.io.BufferedReader; | |||
@@ -1,97 +0,0 @@ | |||
/* | |||
* Copyright © 2014 Jerzy Chalupski | |||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. | |||
* SPDX-License-Identifier: Apache-2.0 | |||
*/ | |||
package com.wireguard.android.widget.fab; | |||
import android.content.Context; | |||
import android.content.res.TypedArray; | |||
import android.graphics.Canvas; | |||
import android.graphics.Paint; | |||
import android.graphics.Paint.Style; | |||
import android.graphics.drawable.Drawable; | |||
import android.graphics.drawable.ShapeDrawable; | |||
import android.graphics.drawable.shapes.Shape; | |||
import android.support.annotation.ColorRes; | |||
import android.support.annotation.DrawableRes; | |||
import android.support.v4.content.ContextCompat; | |||
import android.util.AttributeSet; | |||
import com.wireguard.android.R; | |||
public class AddFloatingActionButton extends FloatingActionButton { | |||
int mPlusColor; | |||
public AddFloatingActionButton(final Context context) { | |||
this(context, null); | |||
} | |||
public AddFloatingActionButton(final Context context, final AttributeSet attrs) { | |||
super(context, attrs); | |||
} | |||
public AddFloatingActionButton(final Context context, final AttributeSet attrs, final int defStyle) { | |||
super(context, attrs, defStyle); | |||
} | |||
@Override | |||
void init(final Context context, final AttributeSet attributeSet) { | |||
final TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.AddFloatingActionButton, 0, 0); | |||
mPlusColor = attr.getColor(R.styleable.AddFloatingActionButton_fab_plusIconColor, FloatingActionButton.getColorFromTheme(context, android.R.attr.colorBackground, android.R.color.white)); | |||
attr.recycle(); | |||
super.init(context, attributeSet); | |||
} | |||
/** | |||
* @return the current Color of plus icon. | |||
*/ | |||
public int getPlusColor() { | |||
return mPlusColor; | |||
} | |||
public void setPlusColor(final int color) { | |||
if (mPlusColor != color) { | |||
mPlusColor = color; | |||
updateBackground(); | |||
} | |||
} | |||
public void setPlusColorResId(@ColorRes final int plusColor) { | |||
setPlusColor(ContextCompat.getColor(getContext(), plusColor)); | |||
} | |||
@Override | |||
public void setIcon(@DrawableRes final int icon) { | |||
throw new UnsupportedOperationException("Use FloatingActionButton if you want to use custom icon"); | |||
} | |||
@Override | |||
Drawable getIconDrawable() { | |||
final float iconSize = getDimension(R.dimen.fab_icon_size); | |||
final float iconHalfSize = iconSize / 2f; | |||
final float plusSize = getDimension(R.dimen.fab_plus_icon_size); | |||
final float plusHalfStroke = getDimension(R.dimen.fab_plus_icon_stroke) / 2f; | |||
final float plusOffset = (iconSize - plusSize) / 2f; | |||
final Shape shape = new Shape() { | |||
@Override | |||
public void draw(final Canvas canvas, final Paint paint) { | |||
canvas.drawRect(plusOffset, iconHalfSize - plusHalfStroke, iconSize - plusOffset, iconHalfSize + plusHalfStroke, paint); | |||
canvas.drawRect(iconHalfSize - plusHalfStroke, plusOffset, iconHalfSize + plusHalfStroke, iconSize - plusOffset, paint); | |||
} | |||
}; | |||
final ShapeDrawable drawable = new ShapeDrawable(shape); | |||
final Paint paint = drawable.getPaint(); | |||
paint.setColor(mPlusColor); | |||
paint.setStyle(Style.FILL); | |||
paint.setAntiAlias(true); | |||
return drawable; | |||
} | |||
} |
@@ -1,446 +0,0 @@ | |||
/* | |||
* Copyright © 2014 Jerzy Chalupski | |||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. | |||
* SPDX-License-Identifier: Apache-2.0 | |||
*/ | |||
package com.wireguard.android.widget.fab; | |||
import android.content.Context; | |||
import android.content.res.TypedArray; | |||
import android.graphics.Canvas; | |||
import android.graphics.Color; | |||
import android.graphics.LinearGradient; | |||
import android.graphics.Paint; | |||
import android.graphics.Paint.Style; | |||
import android.graphics.Rect; | |||
import android.graphics.Shader; | |||
import android.graphics.Shader.TileMode; | |||
import android.graphics.drawable.ColorDrawable; | |||
import android.graphics.drawable.Drawable; | |||
import android.graphics.drawable.LayerDrawable; | |||
import android.graphics.drawable.ShapeDrawable; | |||
import android.graphics.drawable.ShapeDrawable.ShaderFactory; | |||
import android.graphics.drawable.StateListDrawable; | |||
import android.graphics.drawable.shapes.OvalShape; | |||
import android.support.annotation.ColorRes; | |||
import android.support.annotation.DimenRes; | |||
import android.support.annotation.DrawableRes; | |||
import android.support.annotation.IntDef; | |||
import android.support.annotation.NonNull; | |||
import android.support.v4.content.ContextCompat; | |||
import android.support.v7.widget.AppCompatImageButton; | |||
import android.util.AttributeSet; | |||
import android.widget.TextView; | |||
import com.wireguard.android.R; | |||
import java.lang.annotation.Retention; | |||
import java.lang.annotation.RetentionPolicy; | |||
public class FloatingActionButton extends AppCompatImageButton { | |||
public static final int SIZE_NORMAL = 0; | |||
public static final int SIZE_MINI = 1; | |||
int mColorNormal; | |||
int mColorPressed; | |||
int mColorDisabled; | |||
String mTitle; | |||
boolean mStrokeVisible; | |||
@DrawableRes | |||
private int mIcon; | |||
private Drawable mIconDrawable; | |||
private int mSize; | |||
private float mCircleSize; | |||
private float mShadowRadius; | |||
private float mShadowOffset; | |||
private int mDrawableSize; | |||
public FloatingActionButton(final Context context) { | |||
this(context, null); | |||
} | |||
public FloatingActionButton(final Context context, final AttributeSet attrs) { | |||
super(context, attrs); | |||
init(context, attrs); | |||
} | |||
public FloatingActionButton(final Context context, final AttributeSet attrs, final int defStyle) { | |||
super(context, attrs, defStyle); | |||
init(context, attrs); | |||
} | |||
public static int getColorFromTheme(final Context context, final int themeResource, @ColorRes final int fallback) { | |||
final TypedArray a = context.obtainStyledAttributes(new int[]{themeResource}); | |||
try { | |||
return a.getColor(0, ContextCompat.getColor(context, fallback)); | |||
} finally { | |||
a.recycle(); | |||
} | |||
} | |||
void init(final Context context, final AttributeSet attributeSet) { | |||
final TypedArray attr = context.obtainStyledAttributes(attributeSet, | |||
R.styleable.FloatingActionButton, 0, 0); | |||
mColorNormal = attr.getColor(R.styleable.FloatingActionButton_fab_colorNormal, | |||
getColorFromTheme(context, android.R.attr.colorAccent, android.R.color.holo_blue_bright)); | |||
mColorPressed = attr.getColor(R.styleable.FloatingActionButton_fab_colorPressed, | |||
darkenOrLightenColor(mColorNormal)); //TODO(msf): use getColorForState on the accent color from theme instead to get darker states | |||
mColorDisabled = attr.getColor(R.styleable.FloatingActionButton_fab_colorDisabled, | |||
ContextCompat.getColor(context, android.R.color.darker_gray)); //TODO(msf): load from theme | |||
mSize = attr.getInt(R.styleable.FloatingActionButton_fab_size, SIZE_NORMAL); | |||
mIcon = attr.getResourceId(R.styleable.FloatingActionButton_fab_icon, 0); | |||
mTitle = attr.getString(R.styleable.FloatingActionButton_fab_title); | |||
mStrokeVisible = attr.getBoolean(R.styleable.FloatingActionButton_fab_stroke_visible, true); | |||
attr.recycle(); | |||
updateCircleSize(); | |||
mShadowRadius = getDimension(R.dimen.fab_shadow_radius); | |||
mShadowOffset = getDimension(R.dimen.fab_shadow_offset); | |||
updateDrawableSize(); | |||
updateBackground(); | |||
} | |||
private void updateDrawableSize() { | |||
mDrawableSize = (int) (mCircleSize + 2 * mShadowRadius); | |||
} | |||
private void updateCircleSize() { | |||
mCircleSize = getDimension(mSize == SIZE_NORMAL ? R.dimen.fab_size_normal : R.dimen.fab_size_mini); | |||
} | |||
@FAB_SIZE | |||
public int getSize() { | |||
return mSize; | |||
} | |||
public void setSize(@FAB_SIZE final int size) { | |||
if (size != SIZE_MINI && size != SIZE_NORMAL) { | |||
throw new IllegalArgumentException("Use @FAB_SIZE constants only!"); | |||
} | |||
if (mSize != size) { | |||
mSize = size; | |||
updateCircleSize(); | |||
updateDrawableSize(); | |||
updateBackground(); | |||
} | |||
} | |||
public void setIcon(@DrawableRes final int icon) { | |||
if (mIcon != icon) { | |||
mIcon = icon; | |||
mIconDrawable = null; | |||
updateBackground(); | |||
} | |||
} | |||
/** | |||
* @return the current Color for normal state. | |||
*/ | |||
public int getColorNormal() { | |||
return mColorNormal; | |||
} | |||
public void setColorNormal(final int color) { | |||
if (mColorNormal != color) { | |||
mColorNormal = color; | |||
updateBackground(); | |||
} | |||
} | |||
public void setColorNormalResId(@ColorRes final int colorNormal) { | |||
setColorNormal(ContextCompat.getColor(getContext(), colorNormal)); | |||
} | |||
/** | |||
* @return the current color for pressed state. | |||
*/ | |||
public int getColorPressed() { | |||
return mColorPressed; | |||
} | |||
public void setColorPressed(final int color) { | |||
if (mColorPressed != color) { | |||
mColorPressed = color; | |||
updateBackground(); | |||
} | |||
} | |||
public void setColorPressedResId(@ColorRes final int colorPressed) { | |||
setColorPressed(ContextCompat.getColor(getContext(), colorPressed)); | |||
} | |||
/** | |||
* @return the current color for disabled state. | |||
*/ | |||
public int getColorDisabled() { | |||
return mColorDisabled; | |||
} | |||
public void setColorDisabled(final int color) { | |||
if (mColorDisabled != color) { | |||
mColorDisabled = color; | |||
updateBackground(); | |||
} | |||
} | |||
public void setColorDisabledResId(@ColorRes final int colorDisabled) { | |||
setColorDisabled(ContextCompat.getColor(getContext(), colorDisabled)); | |||
} | |||
public boolean isStrokeVisible() { | |||
return mStrokeVisible; | |||
} | |||
public void setStrokeVisible(final boolean visible) { | |||
if (mStrokeVisible != visible) { | |||
mStrokeVisible = visible; | |||
updateBackground(); | |||
} | |||
} | |||
float getDimension(@DimenRes final int id) { | |||
return getResources().getDimension(id); | |||
} | |||
TextView getLabelView() { | |||
return (TextView) getTag(R.id.fab_label); | |||
} | |||
public String getTitle() { | |||
return mTitle; | |||
} | |||
public void setTitle(final String title) { | |||
mTitle = title; | |||
final TextView label = getLabelView(); | |||
if (label != null) { | |||
label.setText(title); | |||
} | |||
} | |||
@Override | |||
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { | |||
super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |||
setMeasuredDimension(mDrawableSize, mDrawableSize); | |||
} | |||
void updateBackground() { | |||
final float strokeWidth = getDimension(R.dimen.fab_stroke_width); | |||
final float halfStrokeWidth = strokeWidth / 2f; | |||
final LayerDrawable layerDrawable = new LayerDrawable( | |||
new Drawable[]{ | |||
//TODO(msf); replace these pngs with programatic elevation | |||
getResources().getDrawable(mSize == SIZE_NORMAL ? R.drawable.fab_bg_normal : R.drawable.fab_bg_mini, null), | |||
createFillDrawable(strokeWidth), | |||
createOuterStrokeDrawable(strokeWidth), | |||
getIconDrawable() | |||
}); | |||
final int iconOffset = (int) (mCircleSize - getDimension(R.dimen.fab_icon_size)) / 2; | |||
final int circleInsetHorizontal = (int) (mShadowRadius); | |||
final int circleInsetTop = (int) (mShadowRadius - mShadowOffset); | |||
final int circleInsetBottom = (int) (mShadowRadius + mShadowOffset); | |||
layerDrawable.setLayerInset(1, | |||
circleInsetHorizontal, | |||
circleInsetTop, | |||
circleInsetHorizontal, | |||
circleInsetBottom); | |||
layerDrawable.setLayerInset(2, | |||
(int) (circleInsetHorizontal - halfStrokeWidth), | |||
(int) (circleInsetTop - halfStrokeWidth), | |||
(int) (circleInsetHorizontal - halfStrokeWidth), | |||
(int) (circleInsetBottom - halfStrokeWidth)); | |||
layerDrawable.setLayerInset(3, | |||
circleInsetHorizontal + iconOffset, | |||
circleInsetTop + iconOffset, | |||
circleInsetHorizontal + iconOffset, | |||
circleInsetBottom + iconOffset); | |||
setBackground(layerDrawable); | |||
} | |||
Drawable getIconDrawable() { | |||
if (mIconDrawable != null) { | |||
return mIconDrawable; | |||
} else if (mIcon != 0) { | |||
return ContextCompat.getDrawable(getContext(), mIcon); | |||
} else { | |||
return new ColorDrawable(Color.TRANSPARENT); | |||
} | |||
} | |||
public void setIconDrawable(@NonNull final Drawable iconDrawable) { | |||
if (mIconDrawable != iconDrawable) { | |||
mIcon = 0; | |||
mIconDrawable = iconDrawable; | |||
updateBackground(); | |||
} | |||
} | |||
private StateListDrawable createFillDrawable(final float strokeWidth) { | |||
final StateListDrawable drawable = new StateListDrawable(); | |||
drawable.addState(new int[]{-android.R.attr.state_enabled}, createCircleDrawable(mColorDisabled, strokeWidth)); | |||
drawable.addState(new int[]{android.R.attr.state_pressed}, createCircleDrawable(mColorPressed, strokeWidth)); | |||
drawable.addState(new int[]{}, createCircleDrawable(mColorNormal, strokeWidth)); | |||
return drawable; | |||
} | |||
private Drawable createCircleDrawable(final int color, final float strokeWidth) { | |||
final int alpha = Color.alpha(color); | |||
final int opaqueColor = opaque(color); | |||
final ShapeDrawable fillDrawable = new ShapeDrawable(new OvalShape()); | |||
final Paint paint = fillDrawable.getPaint(); | |||
paint.setAntiAlias(true); | |||
paint.setColor(opaqueColor); | |||
final Drawable[] layers = { | |||
fillDrawable, | |||
createInnerStrokesDrawable(opaqueColor, strokeWidth) | |||
}; | |||
final LayerDrawable drawable = alpha == 255 || !mStrokeVisible | |||
? new LayerDrawable(layers) | |||
: new TranslucentLayerDrawable(alpha, layers); | |||
final int halfStrokeWidth = (int) (strokeWidth / 2f); | |||
drawable.setLayerInset(1, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth); | |||
return drawable; | |||
} | |||
private static Drawable createOuterStrokeDrawable(final float strokeWidth) { | |||
final ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape()); | |||
final Paint paint = shapeDrawable.getPaint(); | |||
paint.setAntiAlias(true); | |||
paint.setStrokeWidth(strokeWidth); | |||
paint.setStyle(Style.STROKE); | |||
paint.setColor(Color.BLACK); | |||
paint.setAlpha(opacityToAlpha(0.02f)); | |||
return shapeDrawable; | |||
} | |||
private static int opacityToAlpha(final float opacity) { | |||
return (int) (255f * opacity); | |||
} | |||
private static int darkenColor(final int argb) { | |||
return adjustColorBrightness(argb, 0.9f); | |||
} | |||
private static int lightenColor(final int argb) { | |||
return adjustColorBrightness(argb, 1.1f); | |||
} | |||
public static int darkenOrLightenColor(final int argb) { | |||
final float[] hsv = new float[3]; | |||
Color.colorToHSV(argb, hsv); | |||
final float factor; | |||
if (hsv[2] < 0.2) | |||
factor = 1.2f; | |||
else | |||
factor = 0.8f; | |||
hsv[2] = Math.min(hsv[2] * factor, 1f); | |||
return Color.HSVToColor(Color.alpha(argb), hsv); | |||
} | |||
private static int adjustColorBrightness(final int argb, final float factor) { | |||
final float[] hsv = new float[3]; | |||
Color.colorToHSV(argb, hsv); | |||
hsv[2] = Math.min(hsv[2] * factor, 1f); | |||
return Color.HSVToColor(Color.alpha(argb), hsv); | |||
} | |||
private static int halfTransparent(final int argb) { | |||
return Color.argb( | |||
Color.alpha(argb) / 2, | |||
Color.red(argb), | |||
Color.green(argb), | |||
Color.blue(argb) | |||
); | |||
} | |||
private static int opaque(final int argb) { | |||
return Color.rgb( | |||
Color.red(argb), | |||
Color.green(argb), | |||
Color.blue(argb) | |||
); | |||
} | |||
private Drawable createInnerStrokesDrawable(final int color, final float strokeWidth) { | |||
if (!mStrokeVisible) { | |||
return new ColorDrawable(Color.TRANSPARENT); | |||
} | |||
final ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape()); | |||
final int bottomStrokeColor = darkenColor(color); | |||
final int bottomStrokeColorHalfTransparent = halfTransparent(bottomStrokeColor); | |||
final int topStrokeColor = lightenColor(color); | |||
final int topStrokeColorHalfTransparent = halfTransparent(topStrokeColor); | |||
final Paint paint = shapeDrawable.getPaint(); | |||
paint.setAntiAlias(true); | |||
paint.setStrokeWidth(strokeWidth); | |||
paint.setStyle(Style.STROKE); | |||
shapeDrawable.setShaderFactory(new ShaderFactory() { | |||
@Override | |||
public Shader resize(int width, int height) { | |||
return new LinearGradient(width / 2, 0, width / 2, height, | |||
new int[]{topStrokeColor, topStrokeColorHalfTransparent, color, bottomStrokeColorHalfTransparent, bottomStrokeColor}, | |||
new float[]{0f, 0.2f, 0.5f, 0.8f, 1f}, | |||
TileMode.CLAMP | |||
); | |||
} | |||
}); | |||
return shapeDrawable; | |||
} | |||
@Override | |||
public void setVisibility(final int visibility) { | |||
final TextView label = getLabelView(); | |||
if (label != null) { | |||
label.setVisibility(visibility); | |||
} | |||
super.setVisibility(visibility); | |||
} | |||
@Retention(RetentionPolicy.SOURCE) | |||
@IntDef({SIZE_NORMAL, SIZE_MINI}) | |||
public @interface FAB_SIZE { | |||
} | |||
private static final class TranslucentLayerDrawable extends LayerDrawable { | |||
private final int mAlpha; | |||
private TranslucentLayerDrawable(final int alpha, final Drawable... layers) { | |||
super(layers); | |||
mAlpha = alpha; | |||
} | |||
@Override | |||
public void draw(final Canvas canvas) { | |||
final Rect bounds = getBounds(); | |||
canvas.saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, mAlpha); | |||
super.draw(canvas); | |||
canvas.restore(); | |||
} | |||
} | |||
} |
@@ -22,6 +22,8 @@ import android.os.Parcel; | |||
import android.os.Parcelable; | |||
import android.support.annotation.Keep; | |||
import android.support.annotation.NonNull; | |||
import android.support.design.widget.FloatingActionButton; | |||
import android.support.v4.content.res.ResourcesCompat; | |||
import android.support.v7.widget.AppCompatTextView; | |||
import android.util.AttributeSet; | |||
import android.view.ContextThemeWrapper; | |||
@@ -50,11 +52,6 @@ public class FloatingActionsMenu extends ViewGroup { | |||
private static final TimeInterpolator EXPAND_INTERPOLATOR = new OvershootInterpolator(); | |||
private static final TimeInterpolator COLLAPSE_INTERPOLATOR = new DecelerateInterpolator(3f); | |||
private static final TimeInterpolator ALPHA_EXPAND_INTERPOLATOR = new DecelerateInterpolator(); | |||
private int mAddButtonPlusColor; | |||
private int mAddButtonColorNormal; | |||
private int mAddButtonColorPressed; | |||
private int mAddButtonSize; | |||
private boolean mAddButtonStrokeVisible; | |||
private int mExpandDirection; | |||
private int mButtonSpacing; | |||
private int mLabelsMargin; | |||
@@ -62,7 +59,7 @@ public class FloatingActionsMenu extends ViewGroup { | |||
private boolean mExpanded; | |||
private final AnimatorSet mExpandAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION); | |||
private final AnimatorSet mCollapseAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION); | |||
private AddFloatingActionButton mAddButton; | |||
private FloatingActionButton mAddButton; | |||
private RotatingDrawable mRotatingDrawable; | |||
private int mMaxButtonWidth; | |||
private int mMaxButtonHeight; | |||
@@ -88,7 +85,7 @@ public class FloatingActionsMenu extends ViewGroup { | |||
} | |||
private void init(final Context context, final AttributeSet attributeSet) { | |||
mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing) - getResources().getDimension(R.dimen.fab_shadow_radius) - getResources().getDimension(R.dimen.fab_shadow_offset)); | |||
mButtonSpacing = (int) (getResources().getDimension(R.dimen.fab_actions_spacing)); | |||
mLabelsMargin = getResources().getDimensionPixelSize(R.dimen.fab_labels_margin); | |||
mLabelsVerticalOffset = getResources().getDimensionPixelSize(R.dimen.fab_shadow_offset); | |||
@@ -96,14 +93,6 @@ public class FloatingActionsMenu extends ViewGroup { | |||
setTouchDelegate(mTouchDelegateGroup); | |||
final TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0); | |||
mAddButtonPlusColor = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonPlusIconColor, | |||
FloatingActionButton.getColorFromTheme(context, android.R.attr.colorBackground, android.R.color.white)); | |||
mAddButtonColorNormal = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorNormal, | |||
FloatingActionButton.getColorFromTheme(context, android.R.attr.colorAccent, android.R.color.holo_blue_bright)); | |||
mAddButtonColorPressed = attr.getColor(R.styleable.FloatingActionsMenu_fab_addButtonColorPressed, | |||
FloatingActionButton.darkenOrLightenColor(mAddButtonColorNormal)); //TODO(msf): use getColorForState on the accent color from theme instead to get darker states | |||
mAddButtonSize = attr.getInt(R.styleable.FloatingActionsMenu_fab_addButtonSize, FloatingActionButton.SIZE_NORMAL); | |||
mAddButtonStrokeVisible = attr.getBoolean(R.styleable.FloatingActionsMenu_fab_addButtonStrokeVisible, true); | |||
mExpandDirection = attr.getInt(R.styleable.FloatingActionsMenu_fab_expandDirection, EXPAND_UP); | |||
mLabelsStyle = attr.getResourceId(R.styleable.FloatingActionsMenu_fab_labelStyle, 0); | |||
mLabelsPosition = attr.getInt(R.styleable.FloatingActionsMenu_fab_labelsPosition, LABELS_ON_LEFT_SIDE); | |||
@@ -125,45 +114,30 @@ public class FloatingActionsMenu extends ViewGroup { | |||
} | |||
private void createAddButton(final Context context) { | |||
mAddButton = new AddFloatingActionButton(context) { | |||
@Override | |||
void updateBackground() { | |||
mPlusColor = mAddButtonPlusColor; | |||
mColorNormal = mAddButtonColorNormal; | |||
mColorPressed = mAddButtonColorPressed; | |||
mStrokeVisible = mAddButtonStrokeVisible; | |||
super.updateBackground(); | |||
} | |||
@Override | |||
Drawable getIconDrawable() { | |||
final RotatingDrawable rotatingDrawable = new RotatingDrawable(super.getIconDrawable()); | |||
mRotatingDrawable = rotatingDrawable; | |||
final RotatingDrawable rotatingDrawable = new RotatingDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_action_add_inverse, context.getTheme())); | |||
mRotatingDrawable = rotatingDrawable; | |||
final TimeInterpolator interpolator = new OvershootInterpolator(); | |||
final TimeInterpolator interpolator = new OvershootInterpolator(); | |||
final ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", EXPANDED_PLUS_ROTATION, COLLAPSED_PLUS_ROTATION); | |||
final ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", COLLAPSED_PLUS_ROTATION, EXPANDED_PLUS_ROTATION); | |||
final ObjectAnimator collapseAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", EXPANDED_PLUS_ROTATION, COLLAPSED_PLUS_ROTATION); | |||
final ObjectAnimator expandAnimator = ObjectAnimator.ofFloat(rotatingDrawable, "rotation", COLLAPSED_PLUS_ROTATION, EXPANDED_PLUS_ROTATION); | |||
collapseAnimator.setInterpolator(interpolator); | |||
expandAnimator.setInterpolator(interpolator); | |||
collapseAnimator.setInterpolator(interpolator); | |||
expandAnimator.setInterpolator(interpolator); | |||
mExpandAnimation.play(expandAnimator); | |||
mCollapseAnimation.play(collapseAnimator); | |||
return rotatingDrawable; | |||
} | |||
}; | |||
mExpandAnimation.play(expandAnimator); | |||
mCollapseAnimation.play(collapseAnimator); | |||
mAddButton = new FloatingActionButton(context); | |||
mAddButton.setImageDrawable(rotatingDrawable); | |||
mAddButton.setId(R.id.fab_expand_menu_button); | |||
mAddButton.setSize(mAddButtonSize); | |||
mAddButton.setOnClickListener(v -> toggle()); | |||
addView(mAddButton, super.generateDefaultLayoutParams()); | |||
mButtonsCount++; | |||
} | |||
public void addButton(final FloatingActionButton button) { | |||
public void addButton(final LabeledFloatingActionButton button) { | |||
addView(button, mButtonsCount - 1); | |||
mButtonsCount++; | |||
@@ -172,7 +146,7 @@ public class FloatingActionsMenu extends ViewGroup { | |||
} | |||
} | |||
public void removeButton(final FloatingActionButton button) { | |||
public void removeButton(final LabeledFloatingActionButton button) { | |||
removeView(button.getLabelView()); | |||
removeView(button); | |||
button.setTag(R.id.fab_label, null); | |||
@@ -257,9 +231,9 @@ public class FloatingActionsMenu extends ViewGroup { | |||
final int addButtonY = expandUp ? b - t - mAddButton.getMeasuredHeight() : 0; | |||
// Ensure mAddButton is centered on the line where the buttons should be | |||
final int buttonsHorizontalCenter = mLabelsPosition == LABELS_ON_LEFT_SIDE | |||
final int buttonsHorizontalCenter = (mLabelsPosition == LABELS_ON_LEFT_SIDE | |||
? r - l - mMaxButtonWidth / 2 | |||
: mMaxButtonWidth / 2; | |||
: mMaxButtonWidth / 2); | |||
final int addButtonLeft = buttonsHorizontalCenter - mAddButton.getMeasuredWidth() / 2; | |||
mAddButton.layout(addButtonLeft, addButtonY, addButtonLeft + mAddButton.getMeasuredWidth(), addButtonY + mAddButton.getMeasuredHeight()); | |||
@@ -314,7 +288,7 @@ public class FloatingActionsMenu extends ViewGroup { | |||
childY - mButtonSpacing / 2, | |||
Math.max(childX + child.getMeasuredWidth(), labelRight), | |||
childY + child.getMeasuredHeight() + mButtonSpacing / 2); | |||
mTouchDelegateGroup.addTouchDelegate(new TouchDelegate(touchArea, child)); | |||
mTouchDelegateGroup.addTouchDelegate(new TouchDelegate(new Rect(touchArea), child)); | |||
label.setTranslationY(mExpanded ? expandedTranslation : collapsedTranslation); | |||
label.setAlpha(mExpanded ? 1f : 0f); | |||
@@ -407,17 +381,17 @@ public class FloatingActionsMenu extends ViewGroup { | |||
for (int i = 0; i < mButtonsCount; i++) { | |||
final FloatingActionButton button = (FloatingActionButton) getChildAt(i); | |||
final String title = button.getTitle(); | |||
if (button == mAddButton || title == null || | |||
button.getTag(R.id.fab_label) != null) continue; | |||
if (button instanceof LabeledFloatingActionButton) { | |||
final String title = ((LabeledFloatingActionButton) button).getTitle(); | |||
final AppCompatTextView label = new AppCompatTextView(context); | |||
label.setTextAppearance(context, mLabelsStyle); | |||
label.setText(button.getTitle()); | |||
addView(label); | |||
final AppCompatTextView label = new AppCompatTextView(context); | |||
label.setTextAppearance(context, mLabelsStyle); | |||
label.setText(title); | |||
addView(label); | |||
button.setTag(R.id.fab_label, label); | |||
button.setTag(R.id.fab_label, label); | |||
} | |||
} | |||
} | |||
@@ -0,0 +1,55 @@ | |||
/* | |||
* Copyright © 2014 Jerzy Chalupski | |||
* Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. | |||
* SPDX-License-Identifier: Apache-2.0 | |||
*/ | |||
package com.wireguard.android.widget.fab; | |||
import android.content.Context; | |||
import android.content.res.TypedArray; | |||
import android.support.design.widget.FloatingActionButton; | |||
import android.util.AttributeSet; | |||
import android.widget.TextView; | |||
import com.wireguard.android.R; | |||
public class LabeledFloatingActionButton extends FloatingActionButton { | |||
private final String title; | |||
public LabeledFloatingActionButton(final Context context) { | |||
this(context, null); | |||
} | |||
public LabeledFloatingActionButton(final Context context, final AttributeSet attrs) { | |||
this(context, attrs, 0); | |||
} | |||
public LabeledFloatingActionButton(final Context context, final AttributeSet attrs, final int defStyle) { | |||
super(context, attrs, defStyle); | |||
final TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.LabeledFloatingActionButton, 0, 0); | |||
title = attr.getString(R.styleable.LabeledFloatingActionButton_fab_title); | |||
attr.recycle(); | |||
} | |||
TextView getLabelView() { | |||
return (TextView) getTag(R.id.fab_label); | |||
} | |||
public String getTitle() { | |||
return title; | |||
} | |||
@Override | |||
public void setVisibility(final int visibility) { | |||
final TextView label = getLabelView(); | |||
if (label != null) { | |||
label.setVisibility(visibility); | |||
} | |||
super.setVisibility(visibility); | |||
} | |||
} |
@@ -43,13 +43,14 @@ public class TouchDelegateGroup extends TouchDelegate { | |||
@Override | |||
public boolean onTouchEvent(@NonNull final MotionEvent event) { | |||
if (!mEnabled) return false; | |||
if (!mEnabled) | |||
return false; | |||
TouchDelegate delegate = null; | |||
switch (event.getAction()) { | |||
case MotionEvent.ACTION_DOWN: | |||
for (final TouchDelegate touchDelegate : mTouchDelegates) { | |||
for (final TouchDelegate touchDelegate : mTouchDelegates) { | |||
if (touchDelegate.onTouchEvent(event)) { | |||
mCurrentTouchDelegate = touchDelegate; | |||
return true; | |||
@@ -0,0 +1,9 @@ | |||
<vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
android:width="24dp" | |||
android:height="24dp" | |||
android:viewportHeight="24.0" | |||
android:viewportWidth="24.0"> | |||
<path | |||
android:fillColor="?android:attr/colorBackground" | |||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" /> | |||
</vector> |
@@ -23,7 +23,8 @@ | |||
android:id="@+id/main_container" | |||
android:layout_width="match_parent" | |||
android:layout_height="match_parent" | |||
android:background="?android:attr/colorBackground"> | |||
android:background="?android:attr/colorBackground" | |||
android:clipChildren="false"> | |||
<android.support.v7.widget.RecyclerView | |||
android:id="@+id/tunnel_list" | |||
@@ -39,27 +40,27 @@ | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:layout_gravity="bottom|end" | |||
android:layout_margin="8dp" | |||
android:layout_margin="16dp" | |||
app:fab_labelStyle="@style/fab_label" | |||
app:fab_labelsPosition="left" | |||
app:layout_dodgeInsetEdges="bottom"> | |||
android:clipChildren="false" | |||
app:fab_labelsPosition="left" > | |||
<com.wireguard.android.widget.fab.FloatingActionButton | |||
<com.wireguard.android.widget.fab.LabeledFloatingActionButton | |||
android:id="@+id/create_empty" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:onClick="@{fragment::onRequestCreateConfig}" | |||
app:fab_icon="@drawable/ic_action_edit_inverse" | |||
app:fab_size="mini" | |||
app:fabSize="mini" | |||
app:srcCompat="@drawable/ic_action_edit_inverse" | |||
app:fab_title="@string/create_empty" /> | |||
<com.wireguard.android.widget.fab.FloatingActionButton | |||
<com.wireguard.android.widget.fab.LabeledFloatingActionButton | |||
android:id="@+id/create_from_file" | |||
android:layout_width="wrap_content" | |||
android:layout_height="wrap_content" | |||
android:onClick="@{fragment::onRequestImportConfig}" | |||
app:fab_icon="@drawable/ic_action_open_inverse" | |||
app:fab_size="mini" | |||
app:srcCompat="@drawable/ic_action_open_inverse" | |||
app:fabSize="mini" | |||
app:fab_title="@string/create_from_file" /> | |||
</com.wireguard.android.widget.fab.FloatingActionsMenu> | |||
</android.support.design.widget.CoordinatorLayout> | |||
@@ -3,46 +3,18 @@ | |||
<item name="fab_expand_menu_button" type="id"/> | |||
<item name="fab_label" type="id"/> | |||
<dimen name="fab_size_normal">56dp</dimen> | |||
<dimen name="fab_size_mini">40dp</dimen> | |||
<dimen name="fab_icon_size">24dp</dimen> | |||
<dimen name="fab_plus_icon_size">14dp</dimen> | |||
<dimen name="fab_plus_icon_stroke">2dp</dimen> | |||
<dimen name="fab_shadow_offset">3dp</dimen> | |||
<dimen name="fab_shadow_radius">9dp</dimen> | |||
<dimen name="fab_stroke_width">1dp</dimen> | |||
<dimen name="fab_actions_spacing">16dp</dimen> | |||
<dimen name="fab_actions_spacing">24dp</dimen> | |||
<dimen name="fab_labels_margin">8dp</dimen> | |||
<declare-styleable name="FloatingActionButton"> | |||
<attr name="fab_colorPressed" format="color"/> | |||
<attr name="fab_colorDisabled" format="color"/> | |||
<attr name="fab_colorNormal" format="color"/> | |||
<attr name="fab_icon" format="reference"/> | |||
<attr name="fab_size" format="enum"> | |||
<enum name="normal" value="0"/> | |||
<enum name="mini" value="1"/> | |||
</attr> | |||
<declare-styleable name="LabeledFloatingActionButton"> | |||
<attr name="fab_title" format="string"/> | |||
<attr name="fab_stroke_visible" format="boolean"/> | |||
</declare-styleable> | |||
<declare-styleable name="AddFloatingActionButton"> | |||
<attr name="fab_plusIconColor" format="color"/> | |||
</declare-styleable> | |||
<declare-styleable name="FloatingActionsMenu"> | |||
<attr name="fab_addButtonColorPressed" format="color"/> | |||
<attr name="fab_addButtonColorNormal" format="color"/> | |||
<attr name="fab_addButtonSize" format="enum"> | |||
<enum name="normal" value="0"/> | |||
<enum name="mini" value="1"/> | |||
</attr> | |||
<attr name="fab_addButtonPlusIconColor" format="color"/> | |||
<attr name="fab_addButtonStrokeVisible" format="boolean"/> | |||
<attr name="fab_labelStyle" format="reference"/> | |||
<attr name="fab_labelsPosition" format="enum"> | |||
<enum name="left" value="0"/> | |||