package org.thoughtcrime.securesms.util import android.animation.ArgbEvaluator import android.animation.ValueAnimator import android.content.Context import android.graphics.Canvas import android.graphics.Paint import android.util.AttributeSet import android.view.View import android.view.animation.AccelerateDecelerateInterpolator import android.widget.LinearLayout import android.widget.RelativeLayout import androidx.annotation.ColorInt import androidx.annotation.ColorRes import network.loki.messenger.R import kotlin.math.roundToInt interface GlowView { var mainColor: Int var sessionShadowColor: Int } object GlowViewUtilities { fun animateColorIdChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) { val startColor = context.resources.getColorWithID(startColorID, context.theme) val endColor = context.resources.getColorWithID(endColorID, context.theme) val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor) animation.duration = 250 animation.addUpdateListener { animator -> val color = animator.animatedValue as Int view.mainColor = color } animation.start() } fun animateColorChange(view: GlowView, @ColorInt startColor: Int, @ColorInt endColor: Int) { val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor) animation.duration = 250 animation.addUpdateListener { animator -> val color = animator.animatedValue as Int view.mainColor = color } animation.start() } fun animateShadowColorIdChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) { val startColor = context.resources.getColorWithID(startColorID, context.theme) val endColor = context.resources.getColorWithID(endColorID, context.theme) val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor) animation.duration = 250 animation.addUpdateListener { animator -> val color = animator.animatedValue as Int view.sessionShadowColor = color } animation.start() } fun animateShadowColorChange( view: GlowView, @ColorInt startColor: Int, @ColorInt endColor: Int, duration: Long = 250 ) { val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor) animation.duration = duration animation.interpolator = AccelerateDecelerateInterpolator() animation.addUpdateListener { animator -> val color = animator.animatedValue as Int view.sessionShadowColor = color } animation.start() } } class PNModeView : LinearLayout, GlowView { @ColorInt override var mainColor: Int = 0 set(newValue) { field = newValue; paint.color = newValue } @ColorInt var strokeColor: Int = 0 set(newValue) { field = newValue; strokePaint.color = newValue } @ColorInt override var sessionShadowColor: Int = 0 set(newValue) { field = newValue; paint.setShadowLayer(toPx(4, resources).toFloat(), 0.0f, 0.0f, newValue) } private val paint: Paint by lazy { val result = Paint() result.style = Paint.Style.FILL result.isAntiAlias = true result } private val strokePaint: Paint by lazy { val result = Paint() result.style = Paint.Style.STROKE result.isAntiAlias = true result.strokeWidth = toPx(1, resources).toFloat() result } // region Lifecycle constructor(context: Context) : super(context) { } constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { } init { setWillNotDraw(false) } // endregion // region Updating override fun onDraw(c: Canvas) { val w = width.toFloat() val h = height.toFloat() val r = resources.getDimension(R.dimen.pn_option_corner_radius) c.drawRoundRect(0.0f, 0.0f, w, h, r, r, paint) c.drawRoundRect(0.0f, 0.0f, w, h, r, r, strokePaint) super.onDraw(c) } // endregion } class NewConversationButtonImageView : androidx.appcompat.widget.AppCompatImageView, GlowView { @ColorInt override var mainColor: Int = 0 set(newValue) { field = newValue; paint.color = newValue } @ColorInt override var sessionShadowColor: Int = 0 set(newValue) { field = newValue; paint.setShadowLayer(toPx(6, resources).toFloat(), 0.0f, 0.0f, newValue) } private val paint: Paint by lazy { val result = Paint() result.style = Paint.Style.FILL result.isAntiAlias = true result } // region Lifecycle constructor(context: Context) : super(context) { } constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { } init { setWillNotDraw(false) } // endregion // region Updating override fun onDraw(c: Canvas) { val w = width.toFloat() val h = height.toFloat() c.drawCircle(w / 2, h / 2, w / 2, paint) super.onDraw(c) } // endregion } class PathDotView : View, GlowView { @ColorInt override var mainColor: Int = 0 set(newValue) { field = newValue; paint.color = newValue } @ColorInt override var sessionShadowColor: Int = 0 set(newValue) { field = newValue; paint.setShadowLayer(toPx(4, resources).toFloat(), 0.0f, 0.0f, newValue) } private val paint: Paint by lazy { val result = Paint() result.style = Paint.Style.FILL result.isAntiAlias = true result } // region Lifecycle constructor(context: Context) : super(context) { } constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { } init { setWillNotDraw(false) } // endregion // region Updating override fun onDraw(c: Canvas) { val w = width.toFloat() val h = height.toFloat() c.drawCircle(w / 2, h / 2, w / 2, paint) super.onDraw(c) } // endregion } class InputBarButtonImageViewContainer : RelativeLayout, GlowView { @ColorInt override var mainColor: Int = 0 set(newValue) { field = newValue; fillPaint.color = newValue } @ColorInt var strokeColor: Int = 0 set(newValue) { field = newValue; strokePaint.color = newValue } @ColorInt override var sessionShadowColor: Int = 0 // Unused private val fillPaint: Paint by lazy { val result = Paint() result.style = Paint.Style.FILL result.isAntiAlias = true result } private val strokePaint: Paint by lazy { val result = Paint() result.style = Paint.Style.STROKE result.isAntiAlias = true result.strokeWidth = 1.0f result.alpha = (255 * 0.2f).roundToInt() result } // region Lifecycle constructor(context: Context) : super(context) { } constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { } init { setWillNotDraw(false) } // endregion // region Updating override fun onDraw(c: Canvas) { val w = width.toFloat() val h = height.toFloat() c.drawCircle(w / 2, h / 2, w / 2, fillPaint) if (strokeColor != 0) { c.drawCircle(w / 2, h / 2, w / 2, strokePaint) } super.onDraw(c) } // endregion } class MessageBubbleView : androidx.constraintlayout.widget.ConstraintLayout, GlowView { @ColorInt override var mainColor: Int = 0 set(newValue) { field = newValue; paint.color = newValue } @ColorInt override var sessionShadowColor: Int = 0 set(newValue) { field = newValue shadowPaint.setShadowLayer(toPx(10, resources).toFloat(), 0.0f, 0.0f, newValue) if (numShadowRenders == 0) { numShadowRenders = 1 } invalidate() } var cornerRadius: Float = 0f var numShadowRenders: Int = 0 private val paint: Paint by lazy { val result = Paint() result.style = Paint.Style.FILL result.isAntiAlias = true result } private val shadowPaint: Paint by lazy { val result = Paint() result.style = Paint.Style.FILL result.isAntiAlias = true result } // region Lifecycle constructor(context: Context) : super(context) { } constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { } init { setWillNotDraw(false) } // endregion // region Updating override fun onDraw(c: Canvas) { val w = width.toFloat() val h = height.toFloat() (0 until numShadowRenders).forEach { c.drawRoundRect(0f, 0f, w, h, cornerRadius, cornerRadius, shadowPaint) } c.drawRoundRect(0f, 0f, w, h, cornerRadius, cornerRadius, paint) super.onDraw(c) } // endregion }