widget: rewrite in kotlin

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
Jason A. Donenfeld 2020-03-20 02:46:01 -06:00
parent 2fe5b92035
commit 1054e54c89
11 changed files with 359 additions and 460 deletions

View File

@ -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 * SPDX-License-Identifier: Apache-2.0
*/ */
package com.wireguard.android.widget package com.wireguard.android.widget
@ -15,7 +15,6 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu
*/ */
object EdgeToEdge { object EdgeToEdge {
@JvmStatic @JvmStatic
fun setUpRoot(root: ViewGroup) { fun setUpRoot(root: ViewGroup) {
root.systemUiVisibility = root.systemUiVisibility =

View File

@ -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;
}
}

View File

@ -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()
}
}

View File

@ -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);
}
}

View File

@ -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)
}
}

View File

@ -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;
}
}

View File

@ -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()
}
}

View File

@ -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;
}
}

View File

@ -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
}
}
}
}

View File

@ -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);
}
}

View File

@ -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)
}
}