summaryrefslogtreecommitdiffhomepage
path: root/app/src/main
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2020-02-14 15:27:17 +0530
committerGitHub <noreply@github.com>2020-02-14 15:27:17 +0530
commit02ea696070aea1cdf0b71af0639499f87196ec8c (patch)
tree39ea7eb8c9cc2daded826d782cda1bf04c0ffee7 /app/src/main
parentd25702d99ded6946608713e670c6a6ce323f8f87 (diff)
Port tunnel creation UI from Viscerion
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/java/com/wireguard/android/activity/MainActivity.java12
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt106
-rw-r--r--app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java64
-rw-r--r--app/src/main/java/com/wireguard/android/util/Extensions.kt16
-rw-r--r--app/src/main/java/com/wireguard/android/widget/fab/FloatingActionButtonBehavior.java66
-rw-r--r--app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java629
-rw-r--r--app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenuRecyclerViewScrollListener.java27
-rw-r--r--app/src/main/java/com/wireguard/android/widget/fab/LabeledFloatingActionButton.java58
-rw-r--r--app/src/main/java/com/wireguard/android/widget/fab/TouchDelegateGroup.java78
-rw-r--r--app/src/main/res/drawable/fab_label_background.xml10
-rw-r--r--app/src/main/res/layout/add_tunnels_bottom_sheet.xml73
-rw-r--r--app/src/main/res/layout/tunnel_list_fragment.xml38
-rw-r--r--app/src/main/res/values-ldrtl/fab.xml4
-rw-r--r--app/src/main/res/values-night/colors.xml4
-rw-r--r--app/src/main/res/values/colors.xml4
-rw-r--r--app/src/main/res/values/dimens.xml7
-rw-r--r--app/src/main/res/values/fab.xml31
-rw-r--r--app/src/main/res/values/styles.xml17
18 files changed, 233 insertions, 1011 deletions
diff --git a/app/src/main/java/com/wireguard/android/activity/MainActivity.java b/app/src/main/java/com/wireguard/android/activity/MainActivity.java
index ab9a4f8a..e70af6f1 100644
--- a/app/src/main/java/com/wireguard/android/activity/MainActivity.java
+++ b/app/src/main/java/com/wireguard/android/activity/MainActivity.java
@@ -14,13 +14,11 @@ import androidx.fragment.app.FragmentTransaction;
import androidx.appcompat.app.ActionBar;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
import android.widget.LinearLayout;
import com.wireguard.android.R;
import com.wireguard.android.fragment.TunnelDetailFragment;
import com.wireguard.android.fragment.TunnelEditorFragment;
-import com.wireguard.android.fragment.TunnelListFragment;
import com.wireguard.android.model.Tunnel;
/**
@@ -33,16 +31,10 @@ public class MainActivity extends BaseActivity
implements FragmentManager.OnBackStackChangedListener {
@Nullable private ActionBar actionBar;
private boolean isTwoPaneLayout;
- @Nullable private TunnelListFragment listFragment;
@Override
public void onBackPressed() {
final int backStackEntries = getSupportFragmentManager().getBackStackEntryCount();
- // If the action menu is visible and expanded, collapse it instead of navigating back.
- if (isTwoPaneLayout || backStackEntries == 0) {
- if (listFragment != null && listFragment.collapseActionMenu())
- return;
- }
// If the two-pane layout does not have an editor open, going back should exit the app.
if (isTwoPaneLayout && backStackEntries <= 1) {
finish();
@@ -74,12 +66,8 @@ public class MainActivity extends BaseActivity
setContentView(R.layout.main_activity);
actionBar = getSupportActionBar();
isTwoPaneLayout = findViewById(R.id.master_detail_wrapper) instanceof LinearLayout;
- listFragment = (TunnelListFragment) getSupportFragmentManager().findFragmentByTag("LIST");
getSupportFragmentManager().addOnBackStackChangedListener(this);
onBackStackChanged();
- final View actionBarView = findViewById(R.id.action_bar);
- if (actionBarView != null)
- actionBarView.setOnTouchListener((v, e) -> listFragment != null && listFragment.collapseActionMenu());
}
@Override
diff --git a/app/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt b/app/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt
new file mode 100644
index 00000000..3df141be
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/fragment/AddTunnelsSheet.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright © 2020 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.fragment
+
+import android.content.Intent
+import android.graphics.drawable.GradientDrawable
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import android.widget.FrameLayout
+import androidx.fragment.app.Fragment
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.google.zxing.integration.android.IntentIntegrator
+import com.wireguard.android.R
+import com.wireguard.android.activity.TunnelCreatorActivity
+import com.wireguard.android.util.resolveAttribute
+
+class AddTunnelsSheet : BottomSheetDialogFragment() {
+
+ private lateinit var behavior: BottomSheetBehavior<FrameLayout>
+ private val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
+ override fun onSlide(bottomSheet: View, slideOffset: Float) {
+ }
+
+ override fun onStateChanged(bottomSheet: View, newState: Int) {
+ if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
+ dismiss()
+ }
+ }
+ }
+
+ override fun getTheme(): Int {
+ return R.style.BottomSheetDialogTheme
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ if (savedInstanceState != null) dismiss()
+ return inflater.inflate(R.layout.add_tunnels_bottom_sheet, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ view.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
+ override fun onGlobalLayout() {
+ view.viewTreeObserver.removeOnGlobalLayoutListener(this)
+ val dialog = dialog as BottomSheetDialog? ?: return
+ behavior = dialog.behavior
+ behavior.state = BottomSheetBehavior.STATE_EXPANDED
+ behavior.peekHeight = 0
+ behavior.addBottomSheetCallback(bottomSheetCallback)
+ dialog.findViewById<View>(R.id.create_empty)?.setOnClickListener {
+ dismiss()
+ onRequestCreateConfig()
+ }
+ dialog.findViewById<View>(R.id.create_from_file)?.setOnClickListener {
+ dismiss()
+ onRequestImportConfig()
+ }
+ dialog.findViewById<View>(R.id.create_from_qrcode)?.setOnClickListener {
+ dismiss()
+ onRequestScanQRCode()
+ }
+ }
+ })
+ val gradientDrawable = GradientDrawable().apply {
+ setColor(requireContext().resolveAttribute(R.attr.colorBackground))
+ }
+ view.background = gradientDrawable
+ }
+
+ override fun dismiss() {
+ super.dismiss()
+ behavior.removeBottomSheetCallback(bottomSheetCallback)
+ }
+
+ private fun requireTargetFragment(): Fragment {
+ return requireNotNull(targetFragment) { "A target fragment should always be set" }
+ }
+
+ private fun onRequestCreateConfig() {
+ startActivity(Intent(activity, TunnelCreatorActivity::class.java))
+ }
+
+ private fun onRequestImportConfig() {
+ val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "*/*"
+ }
+ requireTargetFragment().startActivityForResult(intent, TunnelListFragment.REQUEST_IMPORT)
+ }
+
+ private fun onRequestScanQRCode() {
+ val integrator = IntentIntegrator.forSupportFragment(requireTargetFragment()).apply {
+ setOrientationLocked(false)
+ setBeepEnabled(false)
+ setPrompt(getString(R.string.qr_code_hint))
+ }
+ integrator.initiateScan(listOf(IntentIntegrator.QR_CODE))
+ }
+}
diff --git a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
index 59260500..c86da99e 100644
--- a/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
+++ b/app/src/main/java/com/wireguard/android/fragment/TunnelListFragment.java
@@ -39,7 +39,6 @@ import com.wireguard.android.databinding.TunnelListItemBinding;
import com.wireguard.android.model.Tunnel;
import com.wireguard.android.util.ErrorMessages;
import com.wireguard.android.widget.MultiselectableRelativeLayout;
-import com.wireguard.android.widget.fab.FloatingActionsMenuRecyclerViewScrollListener;
import com.wireguard.config.BadConfigException;
import com.wireguard.config.Config;
@@ -65,21 +64,14 @@ import java9.util.stream.StreamSupport;
*/
public class TunnelListFragment extends BaseFragment {
- private static final int REQUEST_IMPORT = 1;
+ public static final int REQUEST_IMPORT = 1;
+ private static final int REQUEST_TARGET_FRAGMENT = 2;
private static final String TAG = "WireGuard/" + TunnelListFragment.class.getSimpleName();
private final ActionModeListener actionModeListener = new ActionModeListener();
@Nullable private ActionMode actionMode;
@Nullable private TunnelListFragmentBinding binding;
- public boolean collapseActionMenu() {
- if (binding != null && binding.createMenu.isExpanded()) {
- binding.createMenu.collapse();
- return true;
- }
- return false;
- }
-
private void importTunnel(@NonNull final String configText) {
try {
// Ensure the config text is parseable before proceeding…
@@ -218,21 +210,17 @@ public class TunnelListFragment extends BaseFragment {
}
}
- @SuppressWarnings("deprecation")
@SuppressLint("ClickableViewAccessibility")
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
binding = TunnelListFragmentBinding.inflate(inflater, container, false);
-
- binding.tunnelList.setOnTouchListener((view, motionEvent) -> {
- if (binding != null) {
- binding.createMenu.collapse();
- }
- return false;
+ binding.createFab.setOnClickListener(v -> {
+ final AddTunnelsSheet bottomSheet = new AddTunnelsSheet();
+ bottomSheet.setTargetFragment(this, REQUEST_TARGET_FRAGMENT);
+ bottomSheet.show(requireFragmentManager(), "BOTTOM_SHEET");
});
- binding.tunnelList.setOnScrollListener(new FloatingActionsMenuRecyclerViewScrollListener(binding.createMenu));
binding.executePendingBindings();
return binding.getRoot();
}
@@ -245,36 +233,11 @@ public class TunnelListFragment extends BaseFragment {
@Override
public void onPause() {
- if (binding != null) {
- binding.createMenu.collapse();
- }
super.onPause();
}
public void onRequestCreateConfig(@SuppressWarnings("unused") final View view) {
startActivity(new Intent(getActivity(), TunnelCreatorActivity.class));
- if (binding != null)
- binding.createMenu.collapse();
- }
-
- public void onRequestImportConfig(@SuppressWarnings("unused") final View view) {
- final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
- startActivityForResult(intent, REQUEST_IMPORT);
- if (binding != null)
- binding.createMenu.collapse();
- }
-
- public void onRequestScanQRCode(@SuppressWarnings("unused") final View view) {
- final IntentIntegrator intentIntegrator = IntentIntegrator.forSupportFragment(this);
- intentIntegrator.setOrientationLocked(false);
- intentIntegrator.setBeepEnabled(false);
- intentIntegrator.setPrompt(getString(R.string.qr_code_hint));
- intentIntegrator.initiateScan(Collections.singletonList(IntentIntegrator.QR_CODE));
-
- if (binding != null)
- binding.createMenu.collapse();
}
@Override
@@ -296,6 +259,14 @@ public class TunnelListFragment extends BaseFragment {
});
}
+ private void showSnackbar(final CharSequence message) {
+ if (binding != null) {
+ final Snackbar snackbar = Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG);
+ snackbar.setAnchorView(binding.createFab);
+ snackbar.show();
+ }
+ }
+
private void onTunnelDeletionFinished(final Integer count, @Nullable final Throwable throwable) {
final String message;
if (throwable == null) {
@@ -305,9 +276,7 @@ public class TunnelListFragment extends BaseFragment {
message = getResources().getQuantityString(R.plurals.delete_error, count, count, error);
Log.e(TAG, message, throwable);
}
- if (binding != null) {
- Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
- }
+ showSnackbar(message);
}
private void onTunnelImportFinished(final List<Tunnel> tunnels, final Collection<Throwable> throwables) {
@@ -331,8 +300,7 @@ public class TunnelListFragment extends BaseFragment {
tunnels.size() + throwables.size(),
tunnels.size(), tunnels.size() + throwables.size());
- if (binding != null)
- Snackbar.make(binding.mainContainer, message, Snackbar.LENGTH_LONG).show();
+ showSnackbar(message);
}
@Override
diff --git a/app/src/main/java/com/wireguard/android/util/Extensions.kt b/app/src/main/java/com/wireguard/android/util/Extensions.kt
new file mode 100644
index 00000000..6b528a85
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/util/Extensions.kt
@@ -0,0 +1,16 @@
+/*
+ * Copyright © 2020 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.wireguard.android.util
+
+import android.content.Context
+import android.util.TypedValue
+import androidx.annotation.AttrRes
+
+fun Context.resolveAttribute(@AttrRes attrRes: Int): Int {
+ val typedValue = TypedValue()
+ theme.resolveAttribute(attrRes, typedValue, true)
+ return typedValue.data
+}
diff --git a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionButtonBehavior.java b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionButtonBehavior.java
deleted file mode 100644
index 616e176e..00000000
--- a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionButtonBehavior.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget.fab;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import com.google.android.material.snackbar.Snackbar;
-import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
-import android.util.AttributeSet;
-import android.view.View;
-
-public class FloatingActionButtonBehavior extends CoordinatorLayout.Behavior<FloatingActionsMenu> {
-
- private static final long ANIMATION_DURATION = 250;
- private static final TimeInterpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutSlowInInterpolator();
-
- public FloatingActionButtonBehavior(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- private static void animateChange(final FloatingActionsMenu child, final float destination, final float fullSpan) {
- final float origin = child.getBehaviorYTranslation();
- if (Math.abs(destination - origin) < fullSpan / 2) {
- child.setBehaviorYTranslation(destination);
- return;
- }
- final ValueAnimator animator = new ValueAnimator();
- animator.setFloatValues(origin, destination);
- animator.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
- animator.setDuration((long) (ANIMATION_DURATION * (Math.abs(destination - origin) / fullSpan)));
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator a) {
- child.setBehaviorYTranslation(destination);
- }
- });
- animator.addUpdateListener(a -> child.setBehaviorYTranslation((float) a.getAnimatedValue()));
- animator.start();
- }
-
- @Override
- public boolean layoutDependsOn(final CoordinatorLayout parent, final FloatingActionsMenu child,
- final View dependency) {
- return dependency instanceof Snackbar.SnackbarLayout;
- }
-
- @Override
- public boolean onDependentViewChanged(final CoordinatorLayout parent, final FloatingActionsMenu child,
- final View dependency) {
- animateChange(child, Math.min(0, dependency.getTranslationY() - dependency.getMeasuredHeight()), dependency.getMeasuredHeight());
- return true;
- }
-
- @Override
- public void onDependentViewRemoved(final CoordinatorLayout parent, final FloatingActionsMenu child,
- final View dependency) {
- animateChange(child, 0, dependency.getMeasuredHeight());
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java
deleted file mode 100644
index 4ac747cd..00000000
--- a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenu.java
+++ /dev/null
@@ -1,629 +0,0 @@
-/*
- * Copyright © 2014 Jerzy Chalupski
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget.fab;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Build;
-import android.os.Parcel;
-import android.os.Parcelable;
-import androidx.annotation.Keep;
-import androidx.annotation.Nullable;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
-import androidx.core.content.res.ResourcesCompat;
-import androidx.appcompat.widget.AppCompatTextView;
-import android.util.AttributeSet;
-import android.view.ContextThemeWrapper;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.OvershootInterpolator;
-import android.widget.TextView;
-
-import com.wireguard.android.R;
-
-public class FloatingActionsMenu extends ViewGroup {
- public static final int EXPAND_DOWN = 1;
- public static final int EXPAND_LEFT = 2;
- public static final int EXPAND_RIGHT = 3;
- public static final int EXPAND_UP = 0;
- public static final int LABELS_ON_LEFT_SIDE = 0;
- public static final int LABELS_ON_RIGHT_SIDE = 1;
- private static final TimeInterpolator ALPHA_EXPAND_INTERPOLATOR = new DecelerateInterpolator();
- private static final int ANIMATION_DURATION = 300;
- private static final boolean BROKEN_LABEL_STYLE = Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1 && Build.BRAND.equalsIgnoreCase("ASUS");
- private static final float COLLAPSED_PLUS_ROTATION = 0f;
- private static final TimeInterpolator COLLAPSE_INTERPOLATOR = new DecelerateInterpolator(3f);
- private static final float EXPANDED_PLUS_ROTATION = 90f + 45f;
- private static final TimeInterpolator EXPAND_INTERPOLATOR = new OvershootInterpolator();
- private final AnimatorSet mCollapseAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
- private final AnimatorSet mExpandAnimation = new AnimatorSet().setDuration(ANIMATION_DURATION);
- private final Rect touchArea = new Rect(0, 0, 0, 0);
- private float behaviorYTranslation;
- @Nullable private FloatingActionButton mAddButton;
- private int mButtonSpacing;
- private int mButtonsCount;
- private int mExpandDirection;
- private boolean mExpanded;
- private int mLabelsMargin;
- private int mLabelsPosition;
- private int mLabelsStyle;
- private int mLabelsVerticalOffset;
- @Nullable private OnFloatingActionsMenuUpdateListener mListener;
- private int mMaxButtonHeight;
- private int mMaxButtonWidth;
- @Nullable private RotatingDrawable mRotatingDrawable;
- @Nullable private TouchDelegateGroup mTouchDelegateGroup;
- private float scrollYTranslation;
-
- public FloatingActionsMenu(final Context context) {
- this(context, null);
- }
-
- public FloatingActionsMenu(final Context context, @Nullable final AttributeSet attrs) {
- super(context, attrs);
- init(context, attrs);
- }
-
- public FloatingActionsMenu(final Context context, @Nullable final AttributeSet attrs, final int defStyle) {
- super(context, attrs, defStyle);
- init(context, attrs);
- }
-
- private static int adjustForOvershoot(final int dimension) {
- return dimension * 12 / 10;
- }
-
- public void addButton(final LabeledFloatingActionButton button) {
- addView(button, mButtonsCount - 1);
- mButtonsCount++;
-
- if (mLabelsStyle != 0) {
- createLabels();
- }
- }
-
- public void collapse() {
- collapse(false);
- }
-
- private void collapse(final boolean immediately) {
- if (mExpanded) {
- mExpanded = false;
- mTouchDelegateGroup.setEnabled(false);
- mCollapseAnimation.setDuration(immediately ? 0 : ANIMATION_DURATION);
- mCollapseAnimation.start();
- mExpandAnimation.cancel();
-
- if (mListener != null) {
- mListener.onMenuCollapsed();
- }
- }
- }
-
- public void collapseImmediately() {
- collapse(true);
- }
-
- private void createAddButton(final Context context) {
- final RotatingDrawable rotatingDrawable = new RotatingDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_action_add_white, context.getTheme()));
- mRotatingDrawable = rotatingDrawable;
-
- 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);
-
- collapseAnimator.setInterpolator(interpolator);
- expandAnimator.setInterpolator(interpolator);
-
- mExpandAnimation.play(expandAnimator);
- mCollapseAnimation.play(collapseAnimator);
-
- mAddButton = new FloatingActionButton(context);
- mAddButton.setImageDrawable(rotatingDrawable);
- mAddButton.setId(R.id.fab_expand_menu_button);
- mAddButton.setOnClickListener(v -> toggle());
-
- addView(mAddButton, super.generateDefaultLayoutParams());
- mButtonsCount++;
- }
-
- private void createLabels() {
- final Context context = BROKEN_LABEL_STYLE ? getContext() : new ContextThemeWrapper(getContext(), mLabelsStyle);
-
- for (int i = 0; i < mButtonsCount; i++) {
- final FloatingActionButton button = (FloatingActionButton) getChildAt(i);
-
- if (button instanceof LabeledFloatingActionButton) {
- final String title = ((LabeledFloatingActionButton) button).getTitle();
-
- final AppCompatTextView label = new AppCompatTextView(context);
- if (!BROKEN_LABEL_STYLE)
- label.setTextAppearance(context, mLabelsStyle);
- label.setText(title);
- addView(label);
-
- button.setTag(R.id.fab_label, label);
- }
- }
- }
-
- public void expand() {
- if (!mExpanded) {
- mExpanded = true;
- mTouchDelegateGroup.setEnabled(true);
- mCollapseAnimation.cancel();
- mExpandAnimation.start();
-
- if (mListener != null) {
- mListener.onMenuExpanded();
- }
- }
- }
-
- private boolean expandsHorizontally() {
- return mExpandDirection == EXPAND_LEFT || mExpandDirection == EXPAND_RIGHT;
- }
-
- @Override
- protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(super.generateDefaultLayoutParams());
- }
-
- @Override
- public ViewGroup.LayoutParams generateLayoutParams(final AttributeSet attrs) {
- return new LayoutParams(super.generateLayoutParams(attrs));
- }
-
- @Override
- protected ViewGroup.LayoutParams generateLayoutParams(final ViewGroup.LayoutParams p) {
- return new LayoutParams(super.generateLayoutParams(p));
- }
-
- public float getBehaviorYTranslation() {
- return behaviorYTranslation;
- }
-
- public float getScrollYTranslation() {
- return scrollYTranslation;
- }
-
- private void init(final Context context, @Nullable final AttributeSet attributeSet) {
- 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);
-
- mTouchDelegateGroup = new TouchDelegateGroup(this);
- setTouchDelegate(mTouchDelegateGroup);
-
- final TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionsMenu, 0, 0);
- 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);
- attr.recycle();
-
- if (mLabelsStyle != 0 && expandsHorizontally()) {
- throw new IllegalStateException("Action labels in horizontal expand orientation are not supported");
- }
-
- createAddButton(context);
- }
-
- public boolean isExpanded() {
- return mExpanded;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- bringChildToFront(mAddButton);
- mButtonsCount = getChildCount();
-
- if (mLabelsStyle != 0) {
- createLabels();
- }
- }
-
- @Override
- protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
- switch (mExpandDirection) {
- case EXPAND_UP:
- case EXPAND_DOWN:
- final boolean expandUp = mExpandDirection == EXPAND_UP;
-
- if (changed) {
- mTouchDelegateGroup.clearTouchDelegates();
- }
-
- 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
- ? r - l - mMaxButtonWidth / 2
- : mMaxButtonWidth / 2);
- final int addButtonLeft = buttonsHorizontalCenter - mAddButton.getMeasuredWidth() / 2;
- mAddButton.layout(addButtonLeft, addButtonY, addButtonLeft + mAddButton.getMeasuredWidth(), addButtonY + mAddButton.getMeasuredHeight());
-
- final int labelsOffset = mMaxButtonWidth / 2 + mLabelsMargin;
- final int labelsXNearButton = mLabelsPosition == LABELS_ON_LEFT_SIDE
- ? buttonsHorizontalCenter - labelsOffset
- : buttonsHorizontalCenter + labelsOffset;
-
- int nextY = expandUp ?
- addButtonY - mButtonSpacing :
- addButtonY + mAddButton.getMeasuredHeight() + mButtonSpacing;
-
- for (int i = mButtonsCount - 1; i >= 0; i--) {
- final View child = getChildAt(i);
-
- if (child == mAddButton || child.getVisibility() == GONE) continue;
-
- final int childX = buttonsHorizontalCenter - child.getMeasuredWidth() / 2;
- final int childY = expandUp ? nextY - child.getMeasuredHeight() : nextY;
- child.layout(childX, childY, childX + child.getMeasuredWidth(), childY + child.getMeasuredHeight());
-
- final float collapsedTranslation = addButtonY - childY;
- final float expandedTranslation = 0f;
-
- child.setTranslationY(mExpanded ? expandedTranslation : collapsedTranslation);
- child.setAlpha(mExpanded ? 1f : 0f);
-
- final LayoutParams params = (LayoutParams) child.getLayoutParams();
- params.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation);
- params.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation);
- params.setAnimationsTarget(child);
-
- final View label = (View) child.getTag(R.id.fab_label);
- if (label != null) {
- final int labelXAwayFromButton = mLabelsPosition == LABELS_ON_LEFT_SIDE
- ? labelsXNearButton - label.getMeasuredWidth()
- : labelsXNearButton + label.getMeasuredWidth();
-
- final int labelLeft = mLabelsPosition == LABELS_ON_LEFT_SIDE
- ? labelXAwayFromButton
- : labelsXNearButton;
-
- final int labelRight = mLabelsPosition == LABELS_ON_LEFT_SIDE
- ? labelsXNearButton
- : labelXAwayFromButton;
-
- final int labelTop = childY - mLabelsVerticalOffset + (child.getMeasuredHeight() - label.getMeasuredHeight()) / 2;
-
- label.layout(labelLeft, labelTop, labelRight, labelTop + label.getMeasuredHeight());
-
- touchArea.set(Math.min(childX, labelLeft),
- childY - mButtonSpacing / 2,
- Math.max(childX + child.getMeasuredWidth(), labelRight),
- childY + child.getMeasuredHeight() + mButtonSpacing / 2);
- mTouchDelegateGroup.addTouchDelegate(new TouchDelegate(new Rect(touchArea), child));
-
- label.setTranslationY(mExpanded ? expandedTranslation : collapsedTranslation);
- label.setAlpha(mExpanded ? 1f : 0f);
-
- final LayoutParams labelParams = (LayoutParams) label.getLayoutParams();
- labelParams.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation);
- labelParams.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation);
- labelParams.setAnimationsTarget(label);
- }
-
- nextY = expandUp ?
- childY - mButtonSpacing :
- childY + child.getMeasuredHeight() + mButtonSpacing;
- }
- break;
-
- case EXPAND_LEFT:
- case EXPAND_RIGHT:
- final boolean expandLeft = mExpandDirection == EXPAND_LEFT;
-
- final int addButtonX = expandLeft ? r - l - mAddButton.getMeasuredWidth() : 0;
- // Ensure mAddButton is centered on the line where the buttons should be
- final int addButtonTop = b - t - mMaxButtonHeight + (mMaxButtonHeight - mAddButton.getMeasuredHeight()) / 2;
- mAddButton.layout(addButtonX, addButtonTop, addButtonX + mAddButton.getMeasuredWidth(), addButtonTop + mAddButton.getMeasuredHeight());
-
- int nextX = expandLeft ?
- addButtonX - mButtonSpacing :
- addButtonX + mAddButton.getMeasuredWidth() + mButtonSpacing;
-
- for (int i = mButtonsCount - 1; i >= 0; i--) {
- final View child = getChildAt(i);
-
- if (child == mAddButton || child.getVisibility() == GONE) continue;
-
- final int childX = expandLeft ? nextX - child.getMeasuredWidth() : nextX;
- final int childY = addButtonTop + (mAddButton.getMeasuredHeight() - child.getMeasuredHeight()) / 2;
- child.layout(childX, childY, childX + child.getMeasuredWidth(), childY + child.getMeasuredHeight());
-
- final float collapsedTranslation = addButtonX - childX;
- final float expandedTranslation = 0f;
-
- child.setTranslationX(mExpanded ? expandedTranslation : collapsedTranslation);
- child.setAlpha(mExpanded ? 1f : 0f);
-
- final LayoutParams params = (LayoutParams) child.getLayoutParams();
- params.mCollapseDir.setFloatValues(expandedTranslation, collapsedTranslation);
- params.mExpandDir.setFloatValues(collapsedTranslation, expandedTranslation);
- params.setAnimationsTarget(child);
-
- nextX = expandLeft ?
- childX - mButtonSpacing :
- childX + child.getMeasuredWidth() + mButtonSpacing;
- }
-
- break;
- }
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- measureChildren(widthMeasureSpec, heightMeasureSpec);
-
- int width = 0;
- int height = 0;
-
- mMaxButtonWidth = 0;
- mMaxButtonHeight = 0;
- int maxLabelWidth = 0;
-
- for (int i = 0; i < mButtonsCount; i++) {
- final View child = getChildAt(i);
-
- if (child.getVisibility() == GONE) {
- continue;
- }
-
- switch (mExpandDirection) {
- case EXPAND_UP:
- case EXPAND_DOWN:
- mMaxButtonWidth = Math.max(mMaxButtonWidth, child.getMeasuredWidth());
- height += child.getMeasuredHeight();
- break;
- case EXPAND_LEFT:
- case EXPAND_RIGHT:
- width += child.getMeasuredWidth();
- mMaxButtonHeight = Math.max(mMaxButtonHeight, child.getMeasuredHeight());
- break;
- }
-
- if (!expandsHorizontally()) {
- final TextView label = (TextView) child.getTag(R.id.fab_label);
- if (label != null) {
- maxLabelWidth = Math.max(maxLabelWidth, label.getMeasuredWidth());
- }
- }
- }
-
- if (expandsHorizontally()) {
- height = mMaxButtonHeight;
- } else {
- width = mMaxButtonWidth + (maxLabelWidth > 0 ? maxLabelWidth + mLabelsMargin : 0);
- }
-
- switch (mExpandDirection) {
- case EXPAND_UP:
- case EXPAND_DOWN:
- height += mButtonSpacing * (mButtonsCount - 1);
- height = adjustForOvershoot(height);
- break;
- case EXPAND_LEFT:
- case EXPAND_RIGHT:
- width += mButtonSpacing * (mButtonsCount - 1);
- width = adjustForOvershoot(width);
- break;
- }
-
- setMeasuredDimension(width, height);
- }
-
- @Override
- public void onRestoreInstanceState(final Parcelable state) {
- if (state instanceof SavedState) {
- final SavedState savedState = (SavedState) state;
- mExpanded = savedState.mExpanded;
- mTouchDelegateGroup.setEnabled(mExpanded);
-
- if (mRotatingDrawable != null) {
- mRotatingDrawable.setRotation(mExpanded ? EXPANDED_PLUS_ROTATION : COLLAPSED_PLUS_ROTATION);
- }
-
- super.onRestoreInstanceState(savedState.getSuperState());
- } else {
- super.onRestoreInstanceState(state);
- }
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- final Parcelable superState = super.onSaveInstanceState();
- final SavedState savedState = new SavedState(superState);
- savedState.mExpanded = mExpanded;
-
- return savedState;
- }
-
- public void removeButton(final LabeledFloatingActionButton button) {
- removeView(button.getLabelView());
- removeView(button);
- button.setTag(R.id.fab_label, null);
- mButtonsCount--;
- }
-
- public void setBehaviorYTranslation(final float behaviorYTranslation) {
- this.behaviorYTranslation = behaviorYTranslation;
- setTranslationY(behaviorYTranslation + scrollYTranslation);
- }
-
- @Override
- public void setEnabled(final boolean enabled) {
- super.setEnabled(enabled);
-
- mAddButton.setEnabled(enabled);
- }
-
- public void setOnFloatingActionsMenuUpdateListener(final OnFloatingActionsMenuUpdateListener listener) {
- mListener = listener;
- }
-
- public void setScrollYTranslation(final float scrollYTranslation) {
- this.scrollYTranslation = scrollYTranslation;
- setTranslationY(behaviorYTranslation + scrollYTranslation);
- }
-
- public void toggle() {
- if (mExpanded) {
- collapse();
- } else {
- expand();
- }
- }
-
- public interface OnFloatingActionsMenuUpdateListener {
- void onMenuCollapsed();
-
- void onMenuExpanded();
- }
-
- private static class RotatingDrawable extends LayerDrawable {
- private float mRotation;
-
- RotatingDrawable(final Drawable drawable) {
- super(new Drawable[]{drawable});
- }
-
- @Override
- public void draw(final Canvas canvas) {
- canvas.save();
- canvas.rotate(mRotation, getBounds().centerX(), getBounds().centerY());
- super.draw(canvas);
- canvas.restore();
- }
-
- @SuppressWarnings("UnusedDeclaration")
- public float getRotation() {
- return mRotation;
- }
-
- @Keep
- @SuppressWarnings("UnusedDeclaration")
- public void setRotation(final float rotation) {
- mRotation = rotation;
- invalidateSelf();
- }
- }
-
- public static class SavedState extends BaseSavedState {
- public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
-
- @Override
- public SavedState createFromParcel(final Parcel in) {
- return new SavedState(in);
- }
-
- @Override
- public SavedState[] newArray(final int size) {
- return new SavedState[size];
- }
- };
- private boolean mExpanded;
-
- public SavedState(final Parcelable parcel) {
- super(parcel);
- }
-
- private SavedState(final Parcel in) {
- super(in);
- mExpanded = in.readInt() == 1;
- }
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- super.writeToParcel(out, flags);
- out.writeInt(mExpanded ? 1 : 0);
- }
- }
-
- private class LayoutParams extends ViewGroup.LayoutParams {
-
- private final ObjectAnimator mCollapseAlpha = new ObjectAnimator();
- private final ObjectAnimator mCollapseDir = new ObjectAnimator();
- private final ObjectAnimator mExpandAlpha = new ObjectAnimator();
- private final ObjectAnimator mExpandDir = new ObjectAnimator();
- private boolean animationsSetToPlay;
-
- LayoutParams(final ViewGroup.LayoutParams source) {
- super(source);
-
- mExpandDir.setInterpolator(EXPAND_INTERPOLATOR);
- mExpandAlpha.setInterpolator(ALPHA_EXPAND_INTERPOLATOR);
- mCollapseDir.setInterpolator(COLLAPSE_INTERPOLATOR);
- mCollapseAlpha.setInterpolator(COLLAPSE_INTERPOLATOR);
-
- mCollapseAlpha.setProperty(View.ALPHA);
- mCollapseAlpha.setFloatValues(1f, 0f);
-
- mExpandAlpha.setProperty(View.ALPHA);
- mExpandAlpha.setFloatValues(0f, 1f);
-
- switch (mExpandDirection) {
- case EXPAND_UP:
- case EXPAND_DOWN:
- mCollapseDir.setProperty(View.TRANSLATION_Y);
- mExpandDir.setProperty(View.TRANSLATION_Y);
- break;
- case EXPAND_LEFT:
- case EXPAND_RIGHT:
- mCollapseDir.setProperty(View.TRANSLATION_X);
- mExpandDir.setProperty(View.TRANSLATION_X);
- break;
- }
- }
-
- private void addLayerTypeListener(final Animator animator, final View view) {
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- view.setLayerType(LAYER_TYPE_NONE, null);
- }
-
- @Override
- public void onAnimationStart(final Animator animation) {
- view.setLayerType(LAYER_TYPE_HARDWARE, null);
- }
- });
- }
-
- public void setAnimationsTarget(final View view) {
- mCollapseAlpha.setTarget(view);
- mCollapseDir.setTarget(view);
- mExpandAlpha.setTarget(view);
- mExpandDir.setTarget(view);
-
- // Now that the animations have targets, set them to be played
- if (!animationsSetToPlay) {
- addLayerTypeListener(mExpandDir, view);
- addLayerTypeListener(mCollapseDir, view);
-
- mCollapseAnimation.play(mCollapseAlpha);
- mCollapseAnimation.play(mCollapseDir);
- mExpandAnimation.play(mExpandAlpha);
- mExpandAnimation.play(mExpandDir);
- animationsSetToPlay = true;
- }
- }
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenuRecyclerViewScrollListener.java b/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenuRecyclerViewScrollListener.java
deleted file mode 100644
index e1af4484..00000000
--- a/app/src/main/java/com/wireguard/android/widget/fab/FloatingActionsMenuRecyclerViewScrollListener.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget.fab;
-
-import androidx.recyclerview.widget.RecyclerView;
-
-public class FloatingActionsMenuRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
- private static final float SCALE_FACTOR = 1.5f;
- private final FloatingActionsMenu menu;
-
- public FloatingActionsMenuRecyclerViewScrollListener(final FloatingActionsMenu menu) {
- this.menu = menu;
- }
-
- private static float bound(final float min, final float proposal, final float max) {
- return Math.min(max, Math.max(min, proposal));
- }
-
- @Override
- public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
- super.onScrolled(recyclerView, dx, dy);
- menu.setScrollYTranslation(bound(0, menu.getScrollYTranslation() + dy * SCALE_FACTOR, menu.getMeasuredHeight() - menu.getTranslationY()));
- }
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/fab/LabeledFloatingActionButton.java b/app/src/main/java/com/wireguard/android/widget/fab/LabeledFloatingActionButton.java
deleted file mode 100644
index be94a6e5..00000000
--- a/app/src/main/java/com/wireguard/android/widget/fab/LabeledFloatingActionButton.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright © 2014 Jerzy Chalupski
- * Copyright © 2017-2019 WireGuard LLC. 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 androidx.annotation.Nullable;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-import com.wireguard.android.R;
-
-public class LabeledFloatingActionButton extends FloatingActionButton {
-
- @Nullable private final String title;
-
- public LabeledFloatingActionButton(final Context context) {
- this(context, null);
- }
-
- public LabeledFloatingActionButton(final Context context, @Nullable final AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public LabeledFloatingActionButton(final Context context, @Nullable 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();
- }
-
- @Nullable
- TextView getLabelView() {
- return (TextView) getTag(R.id.fab_label);
- }
-
- @Nullable
- 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);
- }
-
-}
diff --git a/app/src/main/java/com/wireguard/android/widget/fab/TouchDelegateGroup.java b/app/src/main/java/com/wireguard/android/widget/fab/TouchDelegateGroup.java
deleted file mode 100644
index e16d1d3e..00000000
--- a/app/src/main/java/com/wireguard/android/widget/fab/TouchDelegateGroup.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright © 2014 Jerzy Chalupski
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget.fab;
-
-import android.graphics.Rect;
-import androidx.annotation.Nullable;
-import android.view.MotionEvent;
-import android.view.TouchDelegate;
-import android.view.View;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-public class TouchDelegateGroup extends TouchDelegate {
- private static final Rect USELESS_HACKY_RECT = new Rect();
- private final Collection<TouchDelegate> mTouchDelegates = new ArrayList<>();
- @Nullable private TouchDelegate mCurrentTouchDelegate;
- private boolean mEnabled;
-
- public TouchDelegateGroup(final View uselessHackyView) {
- super(USELESS_HACKY_RECT, uselessHackyView);
- }
-
- public void addTouchDelegate(final TouchDelegate touchDelegate) {
- mTouchDelegates.add(touchDelegate);
- }
-
- public void clearTouchDelegates() {
- mTouchDelegates.clear();
- mCurrentTouchDelegate = null;
- }
-
- @Override
- public boolean onTouchEvent(final MotionEvent event) {
- if (!mEnabled)
- return false;
-
- TouchDelegate delegate = null;
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- for (final TouchDelegate touchDelegate : mTouchDelegates) {
- if (touchDelegate.onTouchEvent(event)) {
- mCurrentTouchDelegate = touchDelegate;
- return true;
- }
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- delegate = mCurrentTouchDelegate;
- break;
-
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- delegate = mCurrentTouchDelegate;
- mCurrentTouchDelegate = null;
- break;
- }
-
- return delegate != null && delegate.onTouchEvent(event);
- }
-
- public void removeTouchDelegate(final TouchDelegate touchDelegate) {
- mTouchDelegates.remove(touchDelegate);
- if (mCurrentTouchDelegate == touchDelegate) {
- mCurrentTouchDelegate = null;
- }
- }
-
- public void setEnabled(final boolean enabled) {
- mEnabled = enabled;
- }
-}
diff --git a/app/src/main/res/drawable/fab_label_background.xml b/app/src/main/res/drawable/fab_label_background.xml
deleted file mode 100644
index 92c42569..00000000
--- a/app/src/main/res/drawable/fab_label_background.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <corners android:radius="4dp" />
- <padding
- android:bottom="4dp"
- android:left="8dp"
- android:right="8dp"
- android:top="4dp" />
- <solid android:color="@color/fab_label_background_color" />
-</shape>
diff --git a/app/src/main/res/layout/add_tunnels_bottom_sheet.xml b/app/src/main/res/layout/add_tunnels_bottom_sheet.xml
new file mode 100644
index 00000000..62f168b1
--- /dev/null
+++ b/app/src/main/res/layout/add_tunnels_bottom_sheet.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/root"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/bottom_sheet_top_padding">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/create_empty"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bottom_sheet_item_height"
+ android:layout_marginLeft="@dimen/normal_margin"
+ android:layout_marginRight="@dimen/normal_margin"
+ android:layout_marginStart="@dimen/normal_margin"
+ android:layout_marginEnd="@dimen/normal_margin"
+ android:text="@string/create_empty"
+ android:textAlignment="viewStart"
+ android:textColor="?attr/colorOnSurface"
+ app:icon="@drawable/ic_action_edit"
+ app:iconPadding="@dimen/bottom_sheet_icon_padding"
+ app:iconTint="?attr/colorSecondary"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/create_from_file"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:rippleColor="?attr/colorSecondary"
+ style="@style/Widget.MaterialComponents.Button.TextButton.Icon"/>
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/create_from_file"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bottom_sheet_item_height"
+ android:layout_marginLeft="@dimen/normal_margin"
+ android:layout_marginRight="@dimen/normal_margin"
+ android:layout_marginStart="@dimen/normal_margin"
+ android:layout_marginEnd="@dimen/normal_margin"
+ android:text="@string/create_from_file"
+ android:textAlignment="viewStart"
+ android:textColor="?attr/colorOnSurface"
+ app:icon="@drawable/ic_action_open_white"
+ app:iconPadding="@dimen/bottom_sheet_icon_padding"
+ app:iconTint="?attr/colorSecondary"
+ app:layout_constraintTop_toBottomOf="@+id/create_empty"
+ app:layout_constraintBottom_toTopOf="@+id/create_from_qrcode"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:rippleColor="?attr/colorSecondary"
+ style="@style/Widget.MaterialComponents.Button.TextButton.Icon"/>
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/create_from_qrcode"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bottom_sheet_item_height"
+ android:layout_marginLeft="@dimen/normal_margin"
+ android:layout_marginRight="@dimen/normal_margin"
+ android:layout_marginStart="@dimen/normal_margin"
+ android:layout_marginEnd="@dimen/normal_margin"
+ android:text="@string/create_from_qr_code"
+ android:textAlignment="viewStart"
+ android:textColor="?attr/colorOnSurface"
+ app:icon="@drawable/ic_action_scan_qr_code_white"
+ app:iconPadding="@dimen/bottom_sheet_icon_padding"
+ app:iconTint="?attr/colorSecondary"
+ app:layout_constraintTop_toBottomOf="@+id/create_from_file"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:rippleColor="?attr/colorSecondary"
+ style="@style/Widget.MaterialComponents.Button.TextButton.Icon"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/tunnel_list_fragment.xml b/app/src/main/res/layout/tunnel_list_fragment.xml
index 4189fa5f..c4247019 100644
--- a/app/src/main/res/layout/tunnel_list_fragment.xml
+++ b/app/src/main/res/layout/tunnel_list_fragment.xml
@@ -65,44 +65,14 @@
android:text="@string/tunnel_list_placeholder"
android:textSize="20sp" />
</LinearLayout>
-
- <com.wireguard.android.widget.fab.FloatingActionsMenu
- android:id="@+id/create_menu"
+ <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
+ style="@style/Widget.MaterialComponents.ExtendedFloatingActionButton.Icon"
+ android:id="@+id/create_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
- android:clipChildren="false"
- app:fab_labelStyle="@style/fab_label"
- app:fab_labelsPosition="@integer/label_position"
- app:layout_behavior="com.wireguard.android.widget.fab.FloatingActionButtonBehavior">
+ app:icon="@drawable/ic_action_add_white" />
- <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:fabSize="mini"
- app:fab_title="@string/create_from_file"
- app:srcCompat="@drawable/ic_action_open_white" />
-
- <com.wireguard.android.widget.fab.LabeledFloatingActionButton
- android:id="@+id/create_from_qrcode"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:onClick="@{fragment::onRequestScanQRCode}"
- app:fabSize="mini"
- app:fab_title="@string/create_from_qr_code"
- app:srcCompat="@drawable/ic_action_scan_qr_code_white" />
-
- <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:fabSize="mini"
- app:fab_title="@string/create_empty"
- app:srcCompat="@drawable/ic_action_edit_white" />
- </com.wireguard.android.widget.fab.FloatingActionsMenu>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
diff --git a/app/src/main/res/values-ldrtl/fab.xml b/app/src/main/res/values-ldrtl/fab.xml
deleted file mode 100644
index f612440c..00000000
--- a/app/src/main/res/values-ldrtl/fab.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <integer name="label_position">1</integer>
-</resources> \ No newline at end of file
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
index db267d97..314142d9 100644
--- a/app/src/main/res/values-night/colors.xml
+++ b/app/src/main/res/values-night/colors.xml
@@ -1,9 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <!-- TODO(msf): remove these 2 hard-coded colors and replace with theme colors -->
- <color name="fab_label_text_color">#000000</color>
- <color name="fab_label_background_color">#bbbbbb</color>
-
<!-- Base palette -->
<color name="primary_color">#ff212121</color>
<color name="primary_light_color">#ff484848</color>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 9105fb22..06bcd143 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,9 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
- <!-- TODO(msf): remove these 2 hard-coded colors and replace with theme colors -->
- <color name="fab_label_text_color">#ffffff</color>
- <color name="fab_label_background_color">#444444</color>
-
<!-- Base palette -->
<color name="primary_color">#ffffffff</color>
<color name="primary_light_color">#ffffffff</color>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 92be463c..c6abf8eb 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="fab_margin">16dp</dimen>
-</resources> \ No newline at end of file
+ <dimen name="extra_margin">12dp</dimen>
+ <dimen name="bottom_sheet_item_height">56dp</dimen>
+ <dimen name="normal_margin">8dp</dimen>
+ <dimen name="bottom_sheet_top_padding">8dp</dimen>
+ <dimen name="bottom_sheet_icon_padding">16dp</dimen>
+</resources>
diff --git a/app/src/main/res/values/fab.xml b/app/src/main/res/values/fab.xml
deleted file mode 100644
index 8fbdc724..00000000
--- a/app/src/main/res/values/fab.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <item name="fab_expand_menu_button" type="id" />
- <item name="fab_label" type="id" />
-
- <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">24dp</dimen>
- <dimen name="fab_labels_margin">8dp</dimen>
-
- <declare-styleable name="LabeledFloatingActionButton">
- <attr name="fab_title" format="string" />
- </declare-styleable>
- <declare-styleable name="FloatingActionsMenu">
- <attr name="fab_labelStyle" format="reference" />
- <attr name="fab_labelsPosition" format="enum">
- <enum name="left" value="0" />
- <enum name="right" value="1" />
- </attr>
- <attr name="fab_expandDirection" format="enum">
- <enum name="up" value="0" />
- <enum name="down" value="1" />
- <enum name="left" value="2" />
- <enum name="right" value="3" />
- </attr>
- </declare-styleable>
- <integer name="label_position">0</integer>
-</resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 9f55fd3e..f5af8bce 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -28,6 +28,18 @@
<item name="android:windowBackground">?attr/colorBackground</item>
</style>
+ <style name="BottomSheetDialogTheme" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
+ <item name="android:windowIsFloating">false</item>
+ <item name="android:navigationBarColor">?attr/colorBackground</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ <item name="android:windowTranslucentNavigation">false</item>
+ <item name="android:windowIsTranslucent">false</item>
+ <item name="android:backgroundDimEnabled">true</item>
+ <item name="android:backgroundDimAmount">0.5</item>
+ <item name="android:windowTranslucentStatus">false</item>
+ <item name="android:colorBackground">@android:color/transparent</item>
+ </style>
+
<style name="NoBackgroundTheme" parent="AppTheme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowContentOverlay">@null</item>
@@ -41,9 +53,4 @@
<item name="android:windowExitAnimation">@android:anim/fade_out</item>
</style>
- <style name="fab_label" parent="TextAppearance.AppCompat.Inverse">
- <item name="android:background">@drawable/fab_label_background</item>
- <item name="android:textColor">@color/fab_label_text_color</item>
- </style>
-
</resources>