Skip to content

Commit

Permalink
Display blinking cursor for active symbol (optional)
Browse files Browse the repository at this point in the history
  • Loading branch information
fraggjkee committed Aug 8, 2021
1 parent ed8ce46 commit d610d9b
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 50 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ allprojects {
**Step 2.** Add the dependency
```gradle
dependencies {
implementation "com.github.fraggjkee:sms-confirmation-view:1.4.5"
implementation "com.github.fraggjkee:sms-confirmation-view:1.5.0"
}
```

Expand Down Expand Up @@ -53,6 +53,7 @@ This SMS verification view supports Android's DataBinding framework, including i
Here's the list of available XML attributes:

- `scv_codeLength`: expected confirmation code length. Default value = [4](https://github.com/fraggjkee/SmsConfirmationView/blob/fb2be87c0510a10a95b343f79380de72f6fe7742/library/src/main/java/com/fraggjkee/smsconfirmationview/SmsConfirmationView.kt#L186)
- `scv_showCursor`: controls whether the blinking cursor should be displayed for an active symbol. Default value = `true`. Uses the color specified by `scv_symbolBorderActiveColor`.
- `scv_symbolsSpacing`: gap between individual symbol subviews. Default value = [8dp](https://github.com/fraggjkee/SmsConfirmationView/blob/fb2be87c0510a10a95b343f79380de72f6fe7742/library/src/main/res/values/dimens.xml#L4)
- `scv_symbolWidth`: width of each individual symbol cell. Default value = [42dp](https://github.com/fraggjkee/SmsConfirmationView/blob/fb2be87c0510a10a95b343f79380de72f6fe7742/library/src/main/res/values/dimens.xml#L6)
- `scv_symbolHeight`: height of each individual symbol cell. Default value = [48dp](https://github.com/fraggjkee/SmsConfirmationView/blob/fb2be87c0510a10a95b343f79380de72f6fe7742/library/src/main/res/values/dimens.xml#L7)
Expand Down
Binary file modified images/demo-gif.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ android {
targetSdkVersion 30

versionCode 1
versionName "1.4.5"
versionName "1.5.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,16 @@ class SmsConfirmationView @JvmOverloads constructor(
setupSymbolSubviews()
}

val viewCode = symbolSubviews.map { it.symbol }
val viewCode = symbolSubviews.map { it.state.symbol }
.filterNotNull()
.joinToString(separator = "")
val isViewCodeOutdated = enteredCode != viewCode
if (isViewCodeOutdated) {
symbolSubviews.forEachIndexed { index, view ->
view.symbol = enteredCode.getOrNull(index)
view.isActive = (enteredCode.length == index)
view.state = SymbolView.State(
symbol = enteredCode.getOrNull(index),
isActive = (enteredCode.length == index)
)
}
}
}
Expand All @@ -116,7 +118,7 @@ class SmsConfirmationView @JvmOverloads constructor(

for (i in 0 until codeLength) {
val symbolView = SymbolView(context, style.symbolViewStyle)
symbolView.isActive = (i == enteredCode.length)
symbolView.state = SymbolView.State(isActive = (i == enteredCode.length))
addView(symbolView)

if (i < codeLength.dec()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ var SmsConfirmationView.symbolsSpacing: Int
}
get() = style.symbolsSpacing

/**
* @see R.styleable.SmsConfirmationView_scv_showCursor
*/
var SmsConfirmationView.showCursor: Boolean
set(value) {
val updatedStyle = style.symbolViewStyle.copy(
showCursor = showCursor
)
this.style = style.copy(symbolViewStyle = updatedStyle)
}
get() = style.symbolViewStyle.showCursor

/**
* @see R.styleable.SmsConfirmationView_scv_symbolWidth
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal object SmsConfirmationViewStyleUtils {
if (defaultStyle == null) {
val resources = context.resources
val symbolViewStyle = SymbolView.Style(
showCursor = true,
width = resources.getDimensionPixelSize(R.dimen.symbol_view_width),
height = resources.getDimensionPixelSize(R.dimen.symbol_view_height),
backgroundColor = context.getThemeColor(R.attr.colorSurface),
Expand All @@ -35,57 +36,61 @@ internal object SmsConfirmationViewStyleUtils {
context: Context
): SmsConfirmationView.Style {

val defaultStyle = getDefault(context)
val defaultSymbolStyle = defaultStyle.symbolViewStyle
val defaultStyle: SmsConfirmationView.Style = getDefault(context)
val defaultSymbolStyle: SymbolView.Style = defaultStyle.symbolViewStyle
val typedArray =
context.theme.obtainStyledAttributes(attrs, R.styleable.SmsConfirmationView, 0, 0)

return with(typedArray) {
val symbolWidth = getDimensionPixelSize(
val showCursor: Boolean = getBoolean(
R.styleable.SmsConfirmationView_scv_showCursor,
defaultSymbolStyle.showCursor
)
val symbolWidth: Int = getDimensionPixelSize(
R.styleable.SmsConfirmationView_scv_symbolWidth,
defaultSymbolStyle.width
)
val symbolHeight = getDimensionPixelSize(
val symbolHeight: Int = getDimensionPixelSize(
R.styleable.SmsConfirmationView_scv_symbolHeight,
defaultSymbolStyle.height
)
val symbolBackgroundColor = getColor(
val symbolBackgroundColor: Int = getColor(
R.styleable.SmsConfirmationView_scv_symbolBackgroundColor,
defaultSymbolStyle.backgroundColor
)
val symbolBorderColor = getColor(
val symbolBorderColor: Int = getColor(
R.styleable.SmsConfirmationView_scv_symbolBorderColor,
defaultSymbolStyle.borderColor
)
val symbolBorderActiveColor = getColor(
val symbolBorderActiveColor: Int = getColor(
R.styleable.SmsConfirmationView_scv_symbolBorderActiveColor,
symbolBorderColor
)
val symbolBorderWidth = getDimensionPixelSize(
val symbolBorderWidth: Int = getDimensionPixelSize(
R.styleable.SmsConfirmationView_scv_symbolBorderWidth,
defaultSymbolStyle.borderWidth
)
val symbolTextColor = getColor(
val symbolTextColor: Int = getColor(
R.styleable.SmsConfirmationView_scv_symbolTextColor,
defaultSymbolStyle.textColor
)
val symbolTextSize = getDimensionPixelSize(
val symbolTextSize: Int = getDimensionPixelSize(
R.styleable.SmsConfirmationView_scv_symbolTextSize,
defaultSymbolStyle.textSize
)
val cornerRadius = getDimension(
val cornerRadius: Float = getDimension(
R.styleable.SmsConfirmationView_scv_symbolBorderCornerRadius,
defaultSymbolStyle.borderCornerRadius
)
val codeLength = getInt(
val codeLength: Int = getInt(
R.styleable.SmsConfirmationView_scv_codeLength,
defaultStyle.codeLength
)
val symbolsSpacingPx = getDimensionPixelSize(
val symbolsSpacingPx: Int = getDimensionPixelSize(
R.styleable.SmsConfirmationView_scv_symbolsSpacing,
defaultStyle.symbolsSpacing
)
val smsDetectionMode = getInt(
val smsDetectionMode: SmsConfirmationView.SmsDetectionMode = getInt(
R.styleable.SmsConfirmationView_scv_smsDetectionMode,
SmsConfirmationView.SmsDetectionMode.AUTO.ordinal
).let { SmsConfirmationView.SmsDetectionMode.values()[it] }
Expand All @@ -96,6 +101,7 @@ internal object SmsConfirmationViewStyleUtils {
codeLength = codeLength,
symbolsSpacing = symbolsSpacingPx,
symbolViewStyle = SymbolView.Style(
showCursor = showCursor,
width = symbolWidth,
height = symbolHeight,
backgroundColor = symbolBackgroundColor,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.fraggjkee.smsconfirmationview

import android.animation.Animator
import android.animation.ArgbEvaluator
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
Expand All @@ -10,28 +11,31 @@ import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.Px

private const val textPaintAlphaAnimDuration = 200L
private const val borderPaintAlphaAnimDuration = 300L
private const val textPaintAlphaAnimDuration = 100L
private const val borderPaintAlphaAnimDuration = 200L

private const val cursorAlphaAnimDuration = 500L
private const val cursorAlphaAnimStartDelay = 200L

private const val cursorSymbol = "|"

@SuppressLint("ViewConstructor")
internal class SymbolView(context: Context, private val symbolStyle: Style) : View(context) {

var symbol: Char? = null
set(value) {
if (field == value) return
field = value
textSize = calculateTextSize(symbol)
if (value == null) invalidate()
else animateText()
}
data class State(
val symbol: Char? = null,
val isActive: Boolean = false
)

var isActive: Boolean = false
var state: State = State()
set(value) {
if (field == value) return
field = value
animateBorderColorChange(field)
updateState(state)
}

private val showCursor: Boolean get() = symbolStyle.showCursor

private val desiredW: Int
private val desiredH: Int
private val textSizePx: Int
Expand All @@ -45,14 +49,14 @@ internal class SymbolView(context: Context, private val symbolStyle: Style) : Vi

private var textSize: Size

private var textAnimator: Animator? = null

init {
desiredW = symbolStyle.width
desiredH = symbolStyle.height
textSizePx = symbolStyle.textSize
cornerRadius = symbolStyle.borderCornerRadius

textSize = calculateTextSize(symbol)

backgroundPaint = Paint().apply {
this.color = symbolStyle.backgroundColor
this.style = Paint.Style.FILL
Expand All @@ -72,23 +76,42 @@ internal class SymbolView(context: Context, private val symbolStyle: Style) : Vi
this.typeface = Typeface.DEFAULT_BOLD
this.textAlign = Paint.Align.CENTER
}

textSize = calculateTextSize('0')
}

private fun calculateTextSize(symbol: Char?): Size {
return symbol?.let {
val textBounds = Rect()
textPaint.getTextBounds(it.toString(), 0, 1, textBounds)
Size(textBounds.width(), textBounds.height())
} ?: Size(0, 0)
@Suppress("SameParameterValue")
private fun calculateTextSize(symbol: Char): Size {
val textBounds = Rect()
textPaint.getTextBounds(symbol.toString(), 0, 1, textBounds)
return Size(textBounds.width(), textBounds.height())
}

private fun animateText() {
ObjectAnimator.ofInt(textPaint, "alpha", 0, 255)
.apply {
duration = textPaintAlphaAnimDuration
addUpdateListener { invalidate() }
}
.start()
private fun updateState(state: State) = with(state) {
textAnimator?.cancel()
if (symbol == null && isActive && showCursor) {
textPaint.color = symbolStyle.borderColorActive
textAnimator = ObjectAnimator.ofInt(textPaint, "alpha", 255, 255, 0, 0)
.apply {
duration = cursorAlphaAnimDuration
startDelay = cursorAlphaAnimStartDelay
repeatCount = ObjectAnimator.INFINITE
repeatMode = ObjectAnimator.REVERSE
addUpdateListener { invalidate() }
}
} else {
textPaint.color = symbolStyle.textColor
val startAlpha = if (symbol == null) 255 else 127
val endAlpha = if (symbol == null) 0 else 255
textAnimator = ObjectAnimator.ofInt(textPaint, "alpha", startAlpha, endAlpha)
.apply {
duration = textPaintAlphaAnimDuration
addUpdateListener { invalidate() }
}
}

textAnimator?.start()
animateBorderColorChange(isActive)
}

private fun animateBorderColorChange(isActive: Boolean) {
Expand Down Expand Up @@ -143,14 +166,15 @@ internal class SymbolView(context: Context, private val symbolStyle: Style) : Vi
)

canvas.drawText(
symbol?.toString() ?: "",
if (state.isActive && showCursor) cursorSymbol else state.symbol?.toString() ?: "",
backgroundRect.width() / 2 + borderPaint.strokeWidth / 2,
backgroundRect.height() / 2 + textSize.height / 2 + borderPaint.strokeWidth / 2,
textPaint
)
}

data class Style(
val showCursor: Boolean,
@Px val width: Int,
@Px val height: Int,
@ColorInt val backgroundColor: Int,
Expand Down
1 change: 1 addition & 0 deletions library/src/main/res/values/attrs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<resources>

<declare-styleable name="SmsConfirmationView">
<attr name="scv_showCursor" format="boolean" />
<attr name="scv_symbolWidth" format="dimension" />
<attr name="scv_symbolHeight" format="dimension" />
<attr name="scv_symbolTextColor" format="color" />
Expand Down
5 changes: 3 additions & 2 deletions sample/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:padding="32dp"
tools:context=".MainActivity">

<com.fraggjkee.smsconfirmationview.SmsConfirmationView
Expand All @@ -13,9 +13,10 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:scv_codeLength="4"
app:scv_showCursor="true"
app:scv_smsDetectionMode="manual"
app:scv_symbolBackgroundColor="@android:color/white"
app:scv_symbolBorderActiveColor="@android:color/holo_blue_dark"
app:scv_symbolBorderActiveColor="?colorPrimary"
app:scv_symbolBorderColor="@android:color/black"
app:scv_symbolBorderCornerRadius="8dp"
app:scv_symbolBorderWidth="2dp"
Expand Down

0 comments on commit d610d9b

Please sign in to comment.