diff --git a/app/build.gradle b/app/build.gradle index 3237d35b..9881cb7c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' apply from: 'nonnull.gradle' // Create a variable called keystorePropertiesFile, and initialize it to your @@ -70,9 +71,11 @@ ext { annotationsVersion = '1.1.0' appcompatVersion = '1.1.0' cardviewVersion = '1.0.0' + coordinatorLayoutVersion = '1.1.3' databindingVersion = '3.5.3' materialComponentsVersion = '1.1.0' jsr305Version = '3.0.2' + kotlinVersion = '1.3.61' preferenceVersion = '1.1.0' streamsupportVersion = '1.7.1' threetenabpVersion = '1.2.2' @@ -96,6 +99,8 @@ dependencies { implementation "net.sourceforge.streamsupport:android-retrofuture:$streamsupportVersion" implementation "net.sourceforge.streamsupport:android-retrostreams:$streamsupportVersion" implementation "net.i2p.crypto:eddsa:$eddsaVersion" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" + implementation "androidx.constraintlayout:constraintlayout:$coordinatorLayoutVersion" } tasks.withType(JavaCompile) { 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 + 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(R.id.create_empty)?.setOnClickListener { + dismiss() + onRequestCreateConfig() + } + dialog.findViewById(R.id.create_from_file)?.setOnClickListener { + dismiss() + onRequestImportConfig() + } + dialog.findViewById(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 tunnels, final Collection 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 { - - 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 CREATOR = new Creator() { - - @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 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 @@ - - - - - - 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 @@ + + + + + + + + + + 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" /> - - + app:icon="@drawable/ic_action_add_white" /> - - - - - - 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 @@ - - - 1 - \ 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 @@ - - #000000 - #bbbbbb - #ff212121 #ff484848 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 @@ - - #ffffff - #444444 - #ffffffff #ffffffff 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 @@ 16dp - \ No newline at end of file + 12dp + 56dp + 8dp + 8dp + 16dp + 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 @@ - - - - - - 3dp - 9dp - - 1dp - - 24dp - 8dp - - - - - - - - - - - - - - - - - - 0 - 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 @@ ?attr/colorBackground + + - - diff --git a/build.gradle b/build.gradle index 95970ed0..950065d6 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ allprojects { buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.5.3' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61' } repositories { google()