Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion app/src/main/java/io/shortmesh/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,20 @@ class MainActivity : ComponentActivity() {
}
},
sendCodeCallback = { code, cb ->
cb(false, "Expected error message: $code")
val phoneNumber = authyViewModel.phoneNumber ?: ""
val platform = authyViewModel.selectedPlatform?.name ?: ""
scope.launch {
try {
val response = OtpApi.verify(code, phoneNumber, platform)
if (response.error.isNullOrEmpty()) {
cb(true, response.message ?: "Success")
} else {
cb(false, response.error)
}
} catch (e: Exception) {
cb(false, e.message ?: "Verification failed")
}
}
},
) {
showAuthyWidget = false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.shortmesh.sdk.ui

import android.R.id.message
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
Expand Down Expand Up @@ -42,7 +41,6 @@ import io.shortmesh.sdk.R
import io.shortmesh.sdk.viewmodel.AuthyViewModel
import io.shortmesh.sdk.viewmodel.SupportedPlatformsUiState
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.milliseconds

@Composable
fun VerificationCodeScreen(
Expand All @@ -54,41 +52,42 @@ fun VerificationCodeScreen(
onResult: (Pair<Boolean, String?>, expiresAt: String?) -> Unit) -> Unit = { _, _ -> },
onCancelCallback: () -> Unit,
) {
val otpExpiresInSeconds by viewModel.otpExpiresInSeconds.collectAsState()
var showVerifying by remember{ mutableStateOf(false)}
var error: String? by remember{ mutableStateOf(null) }
val otpExpiresAtMillis by viewModel.otpExpiresAtMillis.collectAsState()
var showVerifying by remember { mutableStateOf(false) }
var error: String? by remember { mutableStateOf(null) }

VerificationCodeScreenComponent(
platformName = viewModel.selectedPlatform?.display_name ?: "",
phoneNumber = viewModel.phoneNumber ?: "",
expiresInSeconds = otpExpiresInSeconds,
expiresAtMillis = otpExpiresAtMillis,
submitCallback = { code ->
error = null
showVerifying = true
submitCallback(code) { status, message ->
if(status) {
showVerifying = false
if (status) {
onVerificationSuccess()
} else {
error = message
}
showVerifying = false
}
},
onCancelCallback = onCancelCallback,
onResendCallback = {
error = null
showVerifying = true
onResendCallback(viewModel.phoneNumber ?: "") { pair, expiresAt ->
if(!pair.first) {
showVerifying = false
if (!pair.first) {
error = pair.second
} else {
viewModel.setOtpExpiresAt(expiresAt)
}
showVerifying = false
}
},
error = error,
verifying = showVerifying
verifying = showVerifying,
onCodeChange = { error = null }
)
}

Expand All @@ -98,23 +97,24 @@ private fun VerificationCodeScreenComponent(
platformName: String = "",
phoneNumber: String = "",
error: String? = null,
expiresInSeconds: Long? = null,
expiresAtMillis: Long? = null,
verifying: Boolean = false,
submitCallback: (code: String) -> Unit = {},
onCancelCallback: () -> Unit = {},
onResendCallback: () -> Unit = {},
onCodeChange: () -> Unit = {},
) {
var code by remember { mutableStateOf("") }
var remainingSeconds by remember { mutableLongStateOf(0L) }
val isExpired = expiresInSeconds != null && remainingSeconds <= 0
val isExpired = expiresAtMillis != null && remainingSeconds <= 0

LaunchedEffect(expiresInSeconds) {
if (expiresInSeconds == null) return@LaunchedEffect
remainingSeconds = expiresInSeconds.coerceAtLeast(0L)
while (remainingSeconds > 0) {
delay(1000L.milliseconds)
remainingSeconds -= 1
LaunchedEffect(expiresAtMillis) {
if (expiresAtMillis == null) return@LaunchedEffect
while (true) {
val now = System.currentTimeMillis()
remainingSeconds = ((expiresAtMillis - now) / 1000).coerceAtLeast(0L)
if (remainingSeconds <= 0) break
delay(1000L)
}
}

Expand All @@ -126,7 +126,7 @@ private fun VerificationCodeScreenComponent(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
if(!error.isNullOrEmpty() || LocalInspectionMode.current) {
if (!error.isNullOrEmpty() || LocalInspectionMode.current) {
Text(
error ?: "This is a sample error message.",
modifier = Modifier.fillMaxWidth(),
Expand All @@ -138,8 +138,11 @@ private fun VerificationCodeScreenComponent(

OutlinedTextField(
value = code,
onValueChange = { code = it },
enabled = true,
onValueChange = {
code = it
onCodeChange()
},
enabled = !verifying,
modifier = Modifier.fillMaxWidth(),
label = { Text(stringResource(R.string.enter_code)) },
placeholder = { Text(stringResource(R.string.enter_code)) },
Expand All @@ -160,7 +163,7 @@ private fun VerificationCodeScreenComponent(
isError = !error.isNullOrEmpty(),
)

if (expiresInSeconds != null) {
if (expiresAtMillis != null) {
Spacer(modifier = Modifier.height(4.dp))
Row(
modifier = Modifier.fillMaxWidth(),
Expand Down Expand Up @@ -212,7 +215,6 @@ private fun VerificationCodeScreenComponent(
Button(
onClick = {
submitCallback(code)
code = ""
},
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(8.dp),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ class AuthyViewModel : ViewModel() {
val listPlatformsUiState: StateFlow<SupportedPlatformsUiState?> =
_listPlatformsUiState.asStateFlow()

private val _otpExpiresInSeconds = MutableStateFlow<Long?>(null)
val otpExpiresInSeconds: StateFlow<Long?> = _otpExpiresInSeconds.asStateFlow()
private val _otpExpiresAtMillis = MutableStateFlow<Long?>(null)
val otpExpiresAtMillis: StateFlow<Long?> = _otpExpiresAtMillis.asStateFlow()

private var baseUrl: String? = null

Expand Down Expand Up @@ -71,11 +71,16 @@ class AuthyViewModel : ViewModel() {
}

fun setOtpExpiresAt(expiresAt: String?) {
_otpExpiresInSeconds.value = parseExpiresInSeconds(expiresAt)
_otpExpiresAtMillis.value = parseExpiresAtMillis(expiresAt)
_listPlatformsUiState.value = SupportedPlatformsUiState.Verify
}

private fun parseExpiresInSeconds(expiresAt: String?): Long? {
fun setOtpExpiresAt(expiresAt: Long?) {
_otpExpiresAtMillis.value = expiresAt
_listPlatformsUiState.value = SupportedPlatformsUiState.Verify
}

private fun parseExpiresAtMillis(expiresAt: String?): Long? {
if (expiresAt.isNullOrBlank()) return null
val formats = listOf(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
Expand All @@ -88,11 +93,11 @@ class AuthyViewModel : ViewModel() {
val sdf = SimpleDateFormat(format, Locale.US)
sdf.timeZone = TimeZone.getTimeZone("UTC")
val date = sdf.parse(expiresAt) ?: continue
return ((date.time - System.currentTimeMillis()) / 1000).coerceAtLeast(0L)
return date.time
} catch (_: Exception) {
// try next format
}
}
return expiresAt.toLongOrNull()?.coerceAtLeast(0L)
return expiresAt.toLongOrNull()
}
}