aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2020-03-20 02:46:01 -0600
committerJason A. Donenfeld <Jason@zx2c4.com>2020-03-20 02:46:01 -0600
commit1054e54c8976e2ed5aef48a485b6ef28ae4ddcb8 (patch)
tree9552fde641c2b1c3e7c2d405dd6205f2b2978d9b
parentEdgeToEdge: move into widget (diff)
downloadwireguard-android-1054e54c8976e2ed5aef48a485b6ef28ae4ddcb8.tar.xz
wireguard-android-1054e54c8976e2ed5aef48a485b6ef28ae4ddcb8.zip
widget: rewrite in kotlin
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/EdgeToEdge.kt3
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.java57
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.kt46
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java61
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.kt49
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/NameInputFilter.java56
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/NameInputFilter.kt45
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/SlashDrawable.java221
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/SlashDrawable.kt174
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.java63
-rw-r--r--ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.kt44
11 files changed, 359 insertions, 460 deletions
diff --git a/ui/src/main/java/com/wireguard/android/widget/EdgeToEdge.kt b/ui/src/main/java/com/wireguard/android/widget/EdgeToEdge.kt
index 544e9ddb..45b83aad 100644
--- a/ui/src/main/java/com/wireguard/android/widget/EdgeToEdge.kt
+++ b/ui/src/main/java/com/wireguard/android/widget/EdgeToEdge.kt
@@ -1,5 +1,5 @@
/*
- * Copyright © 2017-2020 WireGuard LLC. All Rights Reserved.
+ * Copyright © 2020 WireGuard LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.wireguard.android.widget
@@ -15,7 +15,6 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu
*/
object EdgeToEdge {
-
@JvmStatic
fun setUpRoot(root: ViewGroup) {
root.systemUiVisibility =
diff --git a/ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.java b/ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.java
deleted file mode 100644
index 82c421cb..00000000
--- a/ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget;
-
-import android.text.InputFilter;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-
-import com.wireguard.crypto.Key;
-import com.wireguard.util.NonNullForAll;
-
-import androidx.annotation.Nullable;
-
-/**
- * InputFilter for entering WireGuard private/public keys encoded with base64.
- */
-
-@NonNullForAll
-public class KeyInputFilter implements InputFilter {
- private static boolean isAllowed(final char c) {
- return Character.isLetterOrDigit(c) || c == '+' || c == '/';
- }
-
- public static InputFilter newInstance() {
- return new KeyInputFilter();
- }
-
- @Nullable
- @Override
- public CharSequence filter(final CharSequence source,
- final int sStart, final int sEnd,
- final Spanned dest,
- final int dStart, final int dEnd) {
- SpannableStringBuilder replacement = null;
- int rIndex = 0;
- final int dLength = dest.length();
- for (int sIndex = sStart; sIndex < sEnd; ++sIndex) {
- final char c = source.charAt(sIndex);
- final int dIndex = dStart + (sIndex - sStart);
- // Restrict characters to the base64 character set.
- // Ensure adding this character does not push the length over the limit.
- if (((dIndex + 1 < Key.Format.BASE64.getLength() && isAllowed(c)) ||
- (dIndex + 1 == Key.Format.BASE64.getLength() && c == '=')) &&
- dLength + (sIndex - sStart) < Key.Format.BASE64.getLength()) {
- ++rIndex;
- } else {
- if (replacement == null)
- replacement = new SpannableStringBuilder(source, sStart, sEnd);
- replacement.delete(rIndex, rIndex + 1);
- }
- }
- return replacement;
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.kt b/ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.kt
new file mode 100644
index 00000000..951af699
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.widget
+
+import android.text.InputFilter
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import com.wireguard.crypto.Key
+
+/**
+ * InputFilter for entering WireGuard private/public keys encoded with base64.
+ */
+class KeyInputFilter : InputFilter {
+ override fun filter(source: CharSequence,
+ sStart: Int, sEnd: Int,
+ dest: Spanned,
+ dStart: Int, dEnd: Int): CharSequence? {
+ var replacement: SpannableStringBuilder? = null
+ var rIndex = 0
+ val dLength = dest.length
+ for (sIndex in sStart until sEnd) {
+ val c = source[sIndex]
+ val dIndex = dStart + (sIndex - sStart)
+ // Restrict characters to the base64 character set.
+ // Ensure adding this character does not push the length over the limit.
+ if ((dIndex + 1 < Key.Format.BASE64.length && isAllowed(c) ||
+ dIndex + 1 == Key.Format.BASE64.length && c == '=') &&
+ dLength + (sIndex - sStart) < Key.Format.BASE64.length) {
+ ++rIndex
+ } else {
+ if (replacement == null) replacement = SpannableStringBuilder(source, sStart, sEnd)
+ replacement.delete(rIndex, rIndex + 1)
+ }
+ }
+ return replacement
+ }
+
+ companion object {
+ private fun isAllowed(c: Char) = Character.isLetterOrDigit(c) || c == '+' || c == '/'
+
+ @JvmStatic
+ fun newInstance() = KeyInputFilter()
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java b/ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java
deleted file mode 100644
index aa0d08d9..00000000
--- a/ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.RelativeLayout;
-
-import com.wireguard.android.R;
-import com.wireguard.util.NonNullForAll;
-
-@NonNullForAll
-public class MultiselectableRelativeLayout extends RelativeLayout {
- private static final int[] STATE_MULTISELECTED = {R.attr.state_multiselected};
- private boolean multiselected;
-
- public MultiselectableRelativeLayout(final Context context) {
- super(context);
- }
-
- public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public MultiselectableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- @Override
- protected int[] onCreateDrawableState(final int extraSpace) {
- if (multiselected) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
- mergeDrawableStates(drawableState, STATE_MULTISELECTED);
- return drawableState;
- }
- return super.onCreateDrawableState(extraSpace);
- }
-
- public void setMultiSelected(final boolean on) {
- if (!multiselected) {
- multiselected = true;
- refreshDrawableState();
- }
- setActivated(on);
- }
-
- public void setSingleSelected(final boolean on) {
- if (multiselected) {
- multiselected = false;
- refreshDrawableState();
- }
- setActivated(on);
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.kt b/ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.kt
new file mode 100644
index 00000000..69ffefc0
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.RelativeLayout
+import com.wireguard.android.R
+
+class MultiselectableRelativeLayout : RelativeLayout {
+ private var multiselected = false
+
+ constructor(context: Context?) : super(context) {}
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {}
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
+ constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {}
+
+ override fun onCreateDrawableState(extraSpace: Int): IntArray {
+ if (multiselected) {
+ val drawableState = super.onCreateDrawableState(extraSpace + 1)
+ View.mergeDrawableStates(drawableState, STATE_MULTISELECTED)
+ return drawableState
+ }
+ return super.onCreateDrawableState(extraSpace)
+ }
+
+ fun setMultiSelected(on: Boolean) {
+ if (!multiselected) {
+ multiselected = true
+ refreshDrawableState()
+ }
+ isActivated = on
+ }
+
+ fun setSingleSelected(on: Boolean) {
+ if (multiselected) {
+ multiselected = false
+ refreshDrawableState()
+ }
+ isActivated = on
+ }
+
+ companion object {
+ private val STATE_MULTISELECTED = intArrayOf(R.attr.state_multiselected)
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/widget/NameInputFilter.java b/ui/src/main/java/com/wireguard/android/widget/NameInputFilter.java
deleted file mode 100644
index 32f4f262..00000000
--- a/ui/src/main/java/com/wireguard/android/widget/NameInputFilter.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget;
-
-import android.text.InputFilter;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-
-import com.wireguard.android.backend.Tunnel;
-import com.wireguard.util.NonNullForAll;
-
-import androidx.annotation.Nullable;
-
-/**
- * InputFilter for entering WireGuard configuration names (Linux interface names).
- */
-
-@NonNullForAll
-public class NameInputFilter implements InputFilter {
- private static boolean isAllowed(final char c) {
- return Character.isLetterOrDigit(c) || "_=+.-".indexOf(c) >= 0;
- }
-
- public static InputFilter newInstance() {
- return new NameInputFilter();
- }
-
- @Nullable
- @Override
- public CharSequence filter(final CharSequence source,
- final int sStart, final int sEnd,
- final Spanned dest,
- final int dStart, final int dEnd) {
- SpannableStringBuilder replacement = null;
- int rIndex = 0;
- final int dLength = dest.length();
- for (int sIndex = sStart; sIndex < sEnd; ++sIndex) {
- final char c = source.charAt(sIndex);
- final int dIndex = dStart + (sIndex - sStart);
- // Restrict characters to those valid in interfaces.
- // Ensure adding this character does not push the length over the limit.
- if ((dIndex < Tunnel.NAME_MAX_LENGTH && isAllowed(c)) &&
- dLength + (sIndex - sStart) < Tunnel.NAME_MAX_LENGTH) {
- ++rIndex;
- } else {
- if (replacement == null)
- replacement = new SpannableStringBuilder(source, sStart, sEnd);
- replacement.delete(rIndex, rIndex + 1);
- }
- }
- return replacement;
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/widget/NameInputFilter.kt b/ui/src/main/java/com/wireguard/android/widget/NameInputFilter.kt
new file mode 100644
index 00000000..ab894195
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/widget/NameInputFilter.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.widget
+
+import android.text.InputFilter
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import com.wireguard.android.backend.Tunnel
+
+/**
+ * InputFilter for entering WireGuard configuration names (Linux interface names).
+ */
+class NameInputFilter : InputFilter {
+ override fun filter(source: CharSequence,
+ sStart: Int, sEnd: Int,
+ dest: Spanned,
+ dStart: Int, dEnd: Int): CharSequence? {
+ var replacement: SpannableStringBuilder? = null
+ var rIndex = 0
+ val dLength = dest.length
+ for (sIndex in sStart until sEnd) {
+ val c = source[sIndex]
+ val dIndex = dStart + (sIndex - sStart)
+ // Restrict characters to those valid in interfaces.
+ // Ensure adding this character does not push the length over the limit.
+ if (dIndex < Tunnel.NAME_MAX_LENGTH && isAllowed(c) &&
+ dLength + (sIndex - sStart) < Tunnel.NAME_MAX_LENGTH) {
+ ++rIndex
+ } else {
+ if (replacement == null) replacement = SpannableStringBuilder(source, sStart, sEnd)
+ replacement.delete(rIndex, rIndex + 1)
+ }
+ }
+ return replacement
+ }
+
+ companion object {
+ private fun isAllowed(c: Char) = Character.isLetterOrDigit(c) || "_=+.-".indexOf(c) >= 0
+
+ @JvmStatic
+ fun newInstance() = NameInputFilter()
+ }
+}
diff --git a/ui/src/main/java/com/wireguard/android/widget/SlashDrawable.java b/ui/src/main/java/com/wireguard/android/widget/SlashDrawable.java
deleted file mode 100644
index 3cb151af..00000000
--- a/ui/src/main/java/com/wireguard/android/widget/SlashDrawable.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright © 2018 The Android Open Source Project
- * Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget;
-
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.res.ColorStateList;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Path.Direction;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.FloatProperty;
-
-import com.wireguard.util.NonNullForAll;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.IntRange;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-
-@RequiresApi(Build.VERSION_CODES.N)
-@NonNullForAll
-public class SlashDrawable extends Drawable {
-
- private static final float CENTER_X = 10.65f;
- private static final float CENTER_Y = 11.869239f;
- private static final float CORNER_RADIUS = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? 0f : 1f;
- // Draw the slash washington-monument style; rotate to no-u-turn style
- private static final float DEFAULT_ROTATION = -45f;
- private static final long QS_ANIM_LENGTH = 350;
- private static final float SCALE = 24f;
- private static final float SLASH_HEIGHT = 28f;
- // These values are derived in un-rotated (vertical) orientation
- private static final float SLASH_WIDTH = 1.8384776f;
- // Bottom is derived during animation
- private static final float LEFT = (CENTER_X - (SLASH_WIDTH / 2)) / SCALE;
- private static final float RIGHT = (CENTER_X + (SLASH_WIDTH / 2)) / SCALE;
- private static final float TOP = (CENTER_Y - (SLASH_HEIGHT / 2)) / SCALE;
- private static final FloatProperty mSlashLengthProp = new FloatProperty<SlashDrawable>("slashLength") {
- @Override
- public Float get(final SlashDrawable object) {
- return object.mCurrentSlashLength;
- }
-
- @Override
- public void setValue(final SlashDrawable object, final float value) {
- object.mCurrentSlashLength = value;
- }
- };
- private final Drawable mDrawable;
- private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private final Path mPath = new Path();
- private final RectF mSlashRect = new RectF(0, 0, 0, 0);
- private boolean mAnimationEnabled = true;
- // Animate this value on change
- private float mCurrentSlashLength;
- private float mRotation;
- private boolean mSlashed;
-
- public SlashDrawable(final Drawable d) {
- mDrawable = d;
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public void draw(final Canvas canvas) {
- canvas.save();
- final Matrix m = new Matrix();
- final int width = getBounds().width();
- final int height = getBounds().height();
- final float radiusX = scale(CORNER_RADIUS, width);
- final float radiusY = scale(CORNER_RADIUS, height);
- updateRect(
- scale(LEFT, width),
- scale(TOP, height),
- scale(RIGHT, width),
- scale(TOP + mCurrentSlashLength, height)
- );
-
- mPath.reset();
- // Draw the slash vertically
- mPath.addRoundRect(mSlashRect, radiusX, radiusY, Direction.CW);
- // Rotate -45 + desired rotation
- m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2);
- mPath.transform(m);
- canvas.drawPath(mPath, mPaint);
-
- // Rotate back to vertical
- m.setRotate(-mRotation - DEFAULT_ROTATION, width / 2, height / 2);
- mPath.transform(m);
-
- // Draw another rect right next to the first, for clipping
- m.setTranslate(mSlashRect.width(), 0);
- mPath.transform(m);
- mPath.addRoundRect(mSlashRect, 1.0f * width, 1.0f * height, Direction.CW);
- m.setRotate(mRotation + DEFAULT_ROTATION, width / 2, height / 2);
- mPath.transform(m);
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
- canvas.clipPath(mPath, Region.Op.DIFFERENCE);
- else
- canvas.clipOutPath(mPath);
-
- mDrawable.draw(canvas);
- canvas.restore();
- }
-
- @Override
- public int getIntrinsicHeight() {
- return mDrawable.getIntrinsicHeight();
- }
-
- @Override
- public int getIntrinsicWidth() {
- return mDrawable.getIntrinsicWidth();
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public int getOpacity() {
- return PixelFormat.OPAQUE;
- }
-
- @Override
- protected void onBoundsChange(final Rect bounds) {
- super.onBoundsChange(bounds);
- mDrawable.setBounds(bounds);
- }
-
- private float scale(final float frac, final int width) {
- return frac * width;
- }
-
- @Override
- public void setAlpha(@IntRange(from = 0, to = 255) final int alpha) {
- mDrawable.setAlpha(alpha);
- mPaint.setAlpha(alpha);
- }
-
- public void setAnimationEnabled(final boolean enabled) {
- mAnimationEnabled = enabled;
- }
-
- @Override
- public void setColorFilter(@Nullable final ColorFilter colorFilter) {
- mDrawable.setColorFilter(colorFilter);
- mPaint.setColorFilter(colorFilter);
- }
-
- private void setDrawableTintList(@Nullable final ColorStateList tint) {
- mDrawable.setTintList(tint);
- }
-
- public void setRotation(final float rotation) {
- if (mRotation == rotation)
- return;
- mRotation = rotation;
- invalidateSelf();
- }
-
- @SuppressWarnings("unchecked")
- public void setSlashed(final boolean slashed) {
- if (mSlashed == slashed) return;
-
- mSlashed = slashed;
-
- final float end = mSlashed ? SLASH_HEIGHT / SCALE : 0f;
- final float start = mSlashed ? 0f : SLASH_HEIGHT / SCALE;
-
- if (mAnimationEnabled) {
- final ObjectAnimator anim = ObjectAnimator.ofFloat(this, mSlashLengthProp, start, end);
- anim.addUpdateListener((ValueAnimator valueAnimator) -> invalidateSelf());
- anim.setDuration(QS_ANIM_LENGTH);
- anim.start();
- } else {
- mCurrentSlashLength = end;
- invalidateSelf();
- }
- }
-
- @Override
- public void setTint(@ColorInt final int tintColor) {
- super.setTint(tintColor);
- mDrawable.setTint(tintColor);
- mPaint.setColor(tintColor);
- }
-
- @Override
- public void setTintList(@Nullable final ColorStateList tint) {
- super.setTintList(tint);
- setDrawableTintList(tint);
- mPaint.setColor(tint == null ? 0 : tint.getDefaultColor());
- invalidateSelf();
- }
-
- @Override
- public void setTintMode(final Mode tintMode) {
- super.setTintMode(tintMode);
- mDrawable.setTintMode(tintMode);
- }
-
- private void updateRect(final float left, final float top, final float right, final float bottom) {
- mSlashRect.left = left;
- mSlashRect.top = top;
- mSlashRect.right = right;
- mSlashRect.bottom = bottom;
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/widget/SlashDrawable.kt b/ui/src/main/java/com/wireguard/android/widget/SlashDrawable.kt
new file mode 100644
index 00000000..a93e35f5
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/widget/SlashDrawable.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright © 2018 The Android Open Source Project
+ * Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.widget
+
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.content.res.ColorStateList
+import android.graphics.*
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.util.FloatProperty
+import android.util.Property
+import androidx.annotation.ColorInt
+import androidx.annotation.IntRange
+import androidx.annotation.RequiresApi
+
+@RequiresApi(Build.VERSION_CODES.N)
+class SlashDrawable(private val mDrawable: Drawable) : Drawable() {
+ private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val mPath = Path()
+ private val mSlashRect = RectF()
+ private var mAnimationEnabled = true
+ // Animate this value on change
+ private var mCurrentSlashLength = 0f
+ private var mRotation = 0f
+ private var mSlashed = false
+
+ override fun draw(canvas: Canvas) {
+ canvas.save()
+ val m = Matrix()
+ val width = bounds.width()
+ val height = bounds.height()
+ val radiusX = scale(CORNER_RADIUS, width)
+ val radiusY = scale(CORNER_RADIUS, height)
+ updateRect(
+ scale(LEFT, width),
+ scale(TOP, height),
+ scale(RIGHT, width),
+ scale(TOP + mCurrentSlashLength, height)
+ )
+ mPath.reset()
+ // Draw the slash vertically
+ mPath.addRoundRect(mSlashRect, radiusX, radiusY, Path.Direction.CW)
+ // Rotate -45 + desired rotation
+ m.setRotate(mRotation + DEFAULT_ROTATION, width / 2f, height / 2f)
+ mPath.transform(m)
+ canvas.drawPath(mPath, mPaint)
+
+ // Rotate back to vertical
+ m.setRotate(-mRotation - DEFAULT_ROTATION, width / 2f, height / 2f)
+ mPath.transform(m)
+
+ // Draw another rect right next to the first, for clipping
+ m.setTranslate(mSlashRect.width(), 0f)
+ mPath.transform(m)
+ mPath.addRoundRect(mSlashRect, 1f * width, 1f * height, Path.Direction.CW)
+ m.setRotate(mRotation + DEFAULT_ROTATION, width / 2f, height / 2f)
+ mPath.transform(m)
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) canvas.clipPath(mPath, Region.Op.DIFFERENCE) else canvas.clipOutPath(mPath)
+ mDrawable.draw(canvas)
+ canvas.restore()
+ }
+
+ override fun getIntrinsicHeight() = mDrawable.intrinsicHeight
+
+ override fun getIntrinsicWidth() = mDrawable.intrinsicWidth
+
+ override fun getOpacity() = PixelFormat.OPAQUE
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+ mDrawable.bounds = bounds
+ }
+
+ private fun scale(frac: Float, width: Int) = frac * width
+
+ override fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int) {
+ mDrawable.alpha = alpha
+ mPaint.alpha = alpha
+ }
+
+ fun setAnimationEnabled(enabled: Boolean) {
+ mAnimationEnabled = enabled
+ }
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ mDrawable.colorFilter = colorFilter
+ mPaint.colorFilter = colorFilter
+ }
+
+ private fun setDrawableTintList(tint: ColorStateList?) {
+ mDrawable.setTintList(tint)
+ }
+
+ fun setRotation(rotation: Float) {
+ if (mRotation == rotation) return
+ mRotation = rotation
+ invalidateSelf()
+ }
+
+ fun setSlashed(slashed: Boolean) {
+ if (mSlashed == slashed) return
+ mSlashed = slashed
+ val end = if (mSlashed) SLASH_HEIGHT / SCALE else 0f
+ val start = if (mSlashed) 0f else SLASH_HEIGHT / SCALE
+ if (mAnimationEnabled) {
+ val anim = ObjectAnimator.ofFloat(this, mSlashLengthProp, start, end)
+ anim.addUpdateListener { _ -> invalidateSelf() }
+ anim.duration = QS_ANIM_LENGTH
+ anim.start()
+ } else {
+ mCurrentSlashLength = end
+ invalidateSelf()
+ }
+ }
+
+ override fun setTint(@ColorInt tintColor: Int) {
+ super.setTint(tintColor)
+ mDrawable.setTint(tintColor)
+ mPaint.color = tintColor
+ }
+
+ override fun setTintList(tint: ColorStateList?) {
+ super.setTintList(tint)
+ setDrawableTintList(tint)
+ mPaint.color = tint?.defaultColor ?: 0
+ invalidateSelf()
+ }
+
+ override fun setTintMode(tintMode: PorterDuff.Mode?) {
+ super.setTintMode(tintMode)
+ mDrawable.setTintMode(tintMode)
+ }
+
+ private fun updateRect(left: Float, top: Float, right: Float, bottom: Float) {
+ mSlashRect.left = left
+ mSlashRect.top = top
+ mSlashRect.right = right
+ mSlashRect.bottom = bottom
+ }
+
+ companion object {
+ private const val CENTER_X = 10.65f
+ private const val CENTER_Y = 11.869239f
+ private val CORNER_RADIUS = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) 0f else 1f
+
+ // Draw the slash washington-monument style; rotate to no-u-turn style
+ private const val DEFAULT_ROTATION = -45f
+ private const val QS_ANIM_LENGTH: Long = 350
+ private const val SCALE = 24f
+ private const val SLASH_HEIGHT = 28f
+
+ // These values are derived in un-rotated (vertical) orientation
+ private const val SLASH_WIDTH = 1.8384776f
+
+ // Bottom is derived during animation
+ private const val LEFT = (CENTER_X - SLASH_WIDTH / 2) / SCALE
+ private const val RIGHT = (CENTER_X + SLASH_WIDTH / 2) / SCALE
+ private const val TOP = (CENTER_Y - SLASH_HEIGHT / 2) / SCALE
+ private val mSlashLengthProp: FloatProperty<SlashDrawable> = object : FloatProperty<SlashDrawable>("slashLength") {
+ override fun get(obj: SlashDrawable): Float {
+ return obj.mCurrentSlashLength
+ }
+
+ override fun setValue(obj: SlashDrawable, value: Float) {
+ obj.mCurrentSlashLength = value
+ }
+ }
+ }
+
+}
diff --git a/ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.java b/ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.java
deleted file mode 100644
index a0cf2ac6..00000000
--- a/ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright © 2013 The Android Open Source Project
- * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package com.wireguard.android.widget;
-
-import android.content.Context;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.widget.Switch;
-
-import com.wireguard.util.NonNullForAll;
-
-import androidx.annotation.Nullable;
-
-@NonNullForAll
-public class ToggleSwitch extends Switch {
- private boolean isRestoringState;
- @Nullable private OnBeforeCheckedChangeListener listener;
-
- public ToggleSwitch(final Context context) {
- this(context, null);
- }
-
- @SuppressWarnings({"SameParameterValue", "WeakerAccess"})
- public ToggleSwitch(final Context context, @Nullable final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void onRestoreInstanceState(final Parcelable state) {
- isRestoringState = true;
- super.onRestoreInstanceState(state);
- isRestoringState = false;
- }
-
- @Override
- public void setChecked(final boolean checked) {
- if (checked == isChecked())
- return;
- if (isRestoringState || listener == null) {
- super.setChecked(checked);
- return;
- }
- setEnabled(false);
- listener.onBeforeCheckedChanged(this, checked);
- }
-
- public void setCheckedInternal(final boolean checked) {
- super.setChecked(checked);
- setEnabled(true);
- }
-
- public void setOnBeforeCheckedChangeListener(final OnBeforeCheckedChangeListener listener) {
- this.listener = listener;
- }
-
- public interface OnBeforeCheckedChangeListener {
- void onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
- }
-}
diff --git a/ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.kt b/ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.kt
new file mode 100644
index 00000000..c97cb934
--- /dev/null
+++ b/ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright © 2013 The Android Open Source Project
+ * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package com.wireguard.android.widget
+
+import android.content.Context
+import android.os.Parcelable
+import android.util.AttributeSet
+import android.widget.Switch
+
+class ToggleSwitch @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) : Switch(context, attrs) {
+ private var isRestoringState = false
+ private var listener: OnBeforeCheckedChangeListener? = null
+ override fun onRestoreInstanceState(state: Parcelable) {
+ isRestoringState = true
+ super.onRestoreInstanceState(state)
+ isRestoringState = false
+ }
+
+ override fun setChecked(checked: Boolean) {
+ if (checked == isChecked) return
+ if (isRestoringState || listener == null) {
+ super.setChecked(checked)
+ return
+ }
+ isEnabled = false
+ listener!!.onBeforeCheckedChanged(this, checked)
+ }
+
+ fun setCheckedInternal(checked: Boolean) {
+ super.setChecked(checked)
+ isEnabled = true
+ }
+
+ fun setOnBeforeCheckedChangeListener(listener: OnBeforeCheckedChangeListener?) {
+ this.listener = listener
+ }
+
+ interface OnBeforeCheckedChangeListener {
+ fun onBeforeCheckedChanged(toggleSwitch: ToggleSwitch?, checked: Boolean)
+ }
+}