From 1054e54c8976e2ed5aef48a485b6ef28ae4ddcb8 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Fri, 20 Mar 2020 02:46:01 -0600 Subject: [PATCH] widget: rewrite in kotlin Signed-off-by: Jason A. Donenfeld --- .../wireguard/android/widget/EdgeToEdge.kt | 3 +- .../android/widget/KeyInputFilter.java | 57 ----- .../android/widget/KeyInputFilter.kt | 46 ++++ .../widget/MultiselectableRelativeLayout.java | 61 ----- .../widget/MultiselectableRelativeLayout.kt | 49 ++++ .../android/widget/NameInputFilter.java | 56 ----- .../android/widget/NameInputFilter.kt | 45 ++++ .../android/widget/SlashDrawable.java | 221 ------------------ .../wireguard/android/widget/SlashDrawable.kt | 174 ++++++++++++++ .../android/widget/ToggleSwitch.java | 63 ----- .../wireguard/android/widget/ToggleSwitch.kt | 44 ++++ 11 files changed, 359 insertions(+), 460 deletions(-) delete mode 100644 ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.java create mode 100644 ui/src/main/java/com/wireguard/android/widget/KeyInputFilter.kt delete mode 100644 ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.java create mode 100644 ui/src/main/java/com/wireguard/android/widget/MultiselectableRelativeLayout.kt delete mode 100644 ui/src/main/java/com/wireguard/android/widget/NameInputFilter.java create mode 100644 ui/src/main/java/com/wireguard/android/widget/NameInputFilter.kt delete mode 100644 ui/src/main/java/com/wireguard/android/widget/SlashDrawable.java create mode 100644 ui/src/main/java/com/wireguard/android/widget/SlashDrawable.kt delete mode 100644 ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.java create mode 100644 ui/src/main/java/com/wireguard/android/widget/ToggleSwitch.kt 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("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 = object : FloatProperty("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) + } +}