feat(android): add 5-minute countdown timer UI for keygen/sign sessions
Displays remaining time during the 5-minute polling timeout: - Shows countdown in CreateWalletScreen (SessionScreen) - Shows countdown in JoinKeygenScreen (JoiningScreen, KeygenProgressScreen) - Shows countdown in CoSignJoinScreen (JoiningScreen, SigningProgressScreen) - Format: mm:ss with Timer icon in tertiary container card - Countdown starts on all_joined event and stops on session start/cancel 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b30017f3a7
commit
3576af0f25
|
|
@ -242,6 +242,7 @@ fun TssPartyApp(
|
|||
currentRound = currentRound,
|
||||
totalRounds = 9,
|
||||
publicKey = publicKey,
|
||||
countdownSeconds = uiState.countdownSeconds,
|
||||
onCreateSession = { name, t, n, participantName ->
|
||||
viewModel.createKeygenSession(name, t, n, participantName)
|
||||
},
|
||||
|
|
@ -292,6 +293,7 @@ fun TssPartyApp(
|
|||
currentRound = joinKeygenRound,
|
||||
totalRounds = 9,
|
||||
publicKey = joinKeygenPublicKey,
|
||||
countdownSeconds = uiState.countdownSeconds,
|
||||
onValidateInviteCode = { inviteCode ->
|
||||
viewModel.validateInviteCode(inviteCode)
|
||||
},
|
||||
|
|
@ -347,6 +349,7 @@ fun TssPartyApp(
|
|||
currentRound = coSignRound,
|
||||
totalRounds = 9,
|
||||
signature = coSignSignature,
|
||||
countdownSeconds = uiState.countdownSeconds,
|
||||
onValidateInviteCode = { inviteCode ->
|
||||
viewModel.validateSignInviteCode(inviteCode)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -63,6 +63,10 @@ class TssRepository @Inject constructor(
|
|||
// Called when 5-minute polling timeout is reached without session starting
|
||||
private var keygenTimeoutCallback: ((String) -> Unit)? = null
|
||||
|
||||
// Countdown tick callback (set by ViewModel)
|
||||
// Called every second with remaining seconds for UI countdown display
|
||||
private var countdownTickCallback: ((Long) -> Unit)? = null
|
||||
|
||||
// Repository-level CoroutineScope for background tasks
|
||||
// Uses SupervisorJob so individual task failures don't cancel other tasks
|
||||
private val repositoryScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
|
@ -281,6 +285,15 @@ class TssRepository @Inject constructor(
|
|||
keygenTimeoutCallback = callback
|
||||
}
|
||||
|
||||
/**
|
||||
* Set countdown tick callback (called by ViewModel)
|
||||
* This callback is invoked every second with the remaining seconds
|
||||
* for displaying countdown in the UI
|
||||
*/
|
||||
fun setCountdownTickCallback(callback: (Long) -> Unit) {
|
||||
countdownTickCallback = callback
|
||||
}
|
||||
|
||||
/**
|
||||
* Start polling session status as fallback mechanism
|
||||
* This matches Electron's checkAndTriggerKeygen() polling behavior:
|
||||
|
|
@ -301,14 +314,28 @@ class TssRepository @Inject constructor(
|
|||
|
||||
android.util.Log.d("TssRepository", "[POLLING] Starting session status polling for $sessionType session: $sessionId")
|
||||
|
||||
// Notify UI that countdown has started (5 minutes = 300 seconds)
|
||||
countdownTickCallback?.invoke(MAX_WAIT_MS / 1000)
|
||||
|
||||
sessionStatusPollingJob = repositoryScope.launch {
|
||||
val startTime = System.currentTimeMillis()
|
||||
var pollCount = 0
|
||||
var lastTickSecond = MAX_WAIT_MS / 1000
|
||||
|
||||
while (isActive && (System.currentTimeMillis() - startTime) < MAX_WAIT_MS) {
|
||||
pollCount++
|
||||
android.util.Log.d("TssRepository", "[POLLING] Poll #$pollCount for session: $sessionId")
|
||||
|
||||
// Calculate and emit remaining seconds for countdown display
|
||||
val elapsedMs = System.currentTimeMillis() - startTime
|
||||
val remainingSeconds = (MAX_WAIT_MS - elapsedMs) / 1000
|
||||
if (remainingSeconds != lastTickSecond) {
|
||||
lastTickSecond = remainingSeconds
|
||||
withContext(Dispatchers.Main) {
|
||||
countdownTickCallback?.invoke(remainingSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val statusResult = getSessionStatus(sessionId)
|
||||
|
||||
|
|
@ -320,6 +347,11 @@ class TssRepository @Inject constructor(
|
|||
if (status.status == "in_progress") {
|
||||
android.util.Log.d("TssRepository", "[POLLING] Session is in_progress, triggering $sessionType via callback")
|
||||
|
||||
// Clear countdown when session starts
|
||||
withContext(Dispatchers.Main) {
|
||||
countdownTickCallback?.invoke(-1L) // -1 indicates countdown stopped (success)
|
||||
}
|
||||
|
||||
// Create synthetic session_started event to trigger keygen/sign
|
||||
// This matches Electron's behavior of checking status and triggering manually
|
||||
val eventData = SessionEventData(
|
||||
|
|
@ -346,12 +378,18 @@ class TssRepository @Inject constructor(
|
|||
// Check if session failed or was cancelled
|
||||
if (status.status == "failed" || status.status == "cancelled") {
|
||||
android.util.Log.d("TssRepository", "[POLLING] Session ${status.status}, stopping polling")
|
||||
withContext(Dispatchers.Main) {
|
||||
countdownTickCallback?.invoke(-1L) // Clear countdown
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Check if session already completed (keygen finished)
|
||||
if (status.status == "completed") {
|
||||
android.util.Log.d("TssRepository", "[POLLING] Session completed, stopping polling")
|
||||
withContext(Dispatchers.Main) {
|
||||
countdownTickCallback?.invoke(-1L) // Clear countdown
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
},
|
||||
|
|
@ -374,6 +412,7 @@ class TssRepository @Inject constructor(
|
|||
android.util.Log.e("TssRepository", "[POLLING] Timeout after ${elapsedSeconds}s waiting for $sessionType to start")
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
countdownTickCallback?.invoke(0L) // Countdown reached zero
|
||||
keygenTimeoutCallback?.invoke("等待 $sessionType 启动超时(5分钟)")
|
||||
}
|
||||
}
|
||||
|
|
@ -390,6 +429,8 @@ class TssRepository @Inject constructor(
|
|||
fun stopSessionStatusPolling() {
|
||||
sessionStatusPollingJob?.cancel()
|
||||
sessionStatusPollingJob = null
|
||||
// Clear countdown display when polling is stopped
|
||||
countdownTickCallback?.invoke(-1L)
|
||||
android.util.Log.d("TssRepository", "[POLLING] Session status polling stopped")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,16 @@ data class SignSessionInfo(
|
|||
val currentParticipants: Int
|
||||
)
|
||||
|
||||
/**
|
||||
* Format countdown seconds to mm:ss display
|
||||
*/
|
||||
private fun formatCountdown(seconds: Long): String {
|
||||
if (seconds < 0) return ""
|
||||
val minutes = seconds / 60
|
||||
val secs = seconds % 60
|
||||
return "%d:%02d".format(minutes, secs)
|
||||
}
|
||||
|
||||
/**
|
||||
* CoSign Join screen matching service-party-app/src/renderer/src/pages/Sign.tsx
|
||||
* Flow: input → select_share → joining → signing → completed
|
||||
|
|
@ -52,6 +62,7 @@ fun CoSignJoinScreen(
|
|||
currentRound: Int = 0,
|
||||
totalRounds: Int = 9,
|
||||
signature: String? = null,
|
||||
countdownSeconds: Long = -1L, // 5-minute countdown: -1 = not counting, >0 = remaining seconds
|
||||
onValidateInviteCode: (inviteCode: String) -> Unit,
|
||||
onJoinSign: (inviteCode: String, shareId: Long, password: String) -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
|
|
@ -205,12 +216,16 @@ fun CoSignJoinScreen(
|
|||
},
|
||||
onCancel = resetToInput
|
||||
)
|
||||
"joining" -> JoiningScreen(onCancel = resetToInput) // Reset to input state, stay on page
|
||||
"joining" -> JoiningScreen(
|
||||
countdownSeconds = countdownSeconds,
|
||||
onCancel = resetToInput
|
||||
) // Reset to input state, stay on page
|
||||
"signing" -> SigningProgressScreen(
|
||||
sessionStatus = sessionStatus,
|
||||
participants = participants,
|
||||
currentRound = currentRound,
|
||||
totalRounds = totalRounds,
|
||||
countdownSeconds = countdownSeconds,
|
||||
onCancel = resetToInput // Reset to input state, stay on page
|
||||
)
|
||||
"completed" -> SigningCompletedScreen(
|
||||
|
|
@ -699,6 +714,7 @@ private fun SelectShareScreen(
|
|||
|
||||
@Composable
|
||||
private fun JoiningScreen(
|
||||
countdownSeconds: Long = -1L,
|
||||
onCancel: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -729,6 +745,35 @@ private fun JoiningScreen(
|
|||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
// Countdown timer (if counting down)
|
||||
if (countdownSeconds > 0) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Timer,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "等待启动: ${formatCountdown(countdownSeconds)}",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
OutlinedButton(onClick = onCancel) {
|
||||
|
|
@ -745,6 +790,7 @@ private fun SigningProgressScreen(
|
|||
participants: List<String>,
|
||||
currentRound: Int,
|
||||
totalRounds: Int,
|
||||
countdownSeconds: Long = -1L,
|
||||
onCancel: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -804,6 +850,46 @@ private fun SigningProgressScreen(
|
|||
}
|
||||
}
|
||||
|
||||
// Countdown timer (if counting down - waiting for sign to start)
|
||||
if (countdownSeconds > 0) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Timer,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "等待签名启动",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
Text(
|
||||
text = formatCountdown(countdownSeconds),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// Participants card
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ fun CreateWalletScreen(
|
|||
currentRound: Int = 0,
|
||||
totalRounds: Int = 9,
|
||||
publicKey: String? = null,
|
||||
countdownSeconds: Long = -1L, // 5-minute countdown: -1 = not counting, >0 = remaining seconds
|
||||
onCreateSession: (walletName: String, thresholdT: Int, thresholdN: Int, participantName: String) -> Unit,
|
||||
onCopyInviteCode: () -> Unit,
|
||||
onEnterSession: () -> Unit,
|
||||
|
|
@ -115,6 +116,7 @@ fun CreateWalletScreen(
|
|||
totalRounds = totalRounds,
|
||||
publicKey = publicKey,
|
||||
inviteCode = inviteCode,
|
||||
countdownSeconds = countdownSeconds,
|
||||
onCopyInviteCode = onCopyInviteCode,
|
||||
onBackToHome = onBackToHome
|
||||
)
|
||||
|
|
@ -610,6 +612,16 @@ private fun generateQRCodeBitmap(content: String, size: Int): Bitmap? {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format countdown seconds to mm:ss display
|
||||
*/
|
||||
private fun formatCountdown(seconds: Long): String {
|
||||
if (seconds < 0) return ""
|
||||
val minutes = seconds / 60
|
||||
val secs = seconds % 60
|
||||
return "%d:%02d".format(minutes, secs)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SessionScreen(
|
||||
walletName: String,
|
||||
|
|
@ -622,6 +634,7 @@ private fun SessionScreen(
|
|||
totalRounds: Int,
|
||||
publicKey: String?,
|
||||
inviteCode: String?,
|
||||
countdownSeconds: Long = -1L,
|
||||
onCopyInviteCode: () -> Unit,
|
||||
onBackToHome: () -> Unit
|
||||
) {
|
||||
|
|
@ -763,6 +776,46 @@ private fun SessionScreen(
|
|||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
// Countdown timer (if counting down - waiting for keygen to start after all joined)
|
||||
if (countdownSeconds > 0) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Timer,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "等待密钥生成启动",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
Text(
|
||||
text = formatCountdown(countdownSeconds),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
// Progress Bar (if in progress)
|
||||
if (sessionStatus == SessionStatus.IN_PROGRESS) {
|
||||
Card(
|
||||
|
|
|
|||
|
|
@ -32,6 +32,16 @@ data class JoinSessionInfo(
|
|||
val totalParticipants: Int
|
||||
)
|
||||
|
||||
/**
|
||||
* Format countdown seconds to mm:ss display
|
||||
*/
|
||||
private fun formatCountdown(seconds: Long): String {
|
||||
if (seconds < 0) return ""
|
||||
val minutes = seconds / 60
|
||||
val secs = seconds % 60
|
||||
return "%d:%02d".format(minutes, secs)
|
||||
}
|
||||
|
||||
/**
|
||||
* JoinKeygen screen matching service-party-app/src/renderer/src/pages/Join.tsx
|
||||
* Simplified flow without password: input → confirm → joining
|
||||
|
|
@ -47,6 +57,7 @@ fun JoinKeygenScreen(
|
|||
currentRound: Int = 0,
|
||||
totalRounds: Int = 9,
|
||||
publicKey: String? = null,
|
||||
countdownSeconds: Long = -1L, // 5-minute countdown: -1 = not counting, >0 = remaining seconds
|
||||
onValidateInviteCode: (inviteCode: String) -> Unit,
|
||||
onJoinKeygen: (inviteCode: String, password: String) -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
|
|
@ -143,12 +154,16 @@ fun JoinKeygenScreen(
|
|||
},
|
||||
onCancel = resetToInput // Reset to input state, stay on page
|
||||
)
|
||||
"joining" -> JoiningScreen(onCancel = resetToInput) // Reset to input state, stay on page
|
||||
"joining" -> JoiningScreen(
|
||||
countdownSeconds = countdownSeconds,
|
||||
onCancel = resetToInput
|
||||
) // Reset to input state, stay on page
|
||||
"progress" -> KeygenProgressScreen(
|
||||
sessionStatus = sessionStatus,
|
||||
participants = participants,
|
||||
currentRound = currentRound,
|
||||
totalRounds = totalRounds,
|
||||
countdownSeconds = countdownSeconds,
|
||||
onCancel = resetToInput // Reset to input state, stay on page
|
||||
)
|
||||
"completed" -> KeygenCompletedScreen(
|
||||
|
|
@ -512,7 +527,10 @@ private fun ConfirmScreen(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun JoiningScreen(onCancel: () -> Unit) {
|
||||
private fun JoiningScreen(
|
||||
countdownSeconds: Long = -1L,
|
||||
onCancel: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -541,6 +559,35 @@ private fun JoiningScreen(onCancel: () -> Unit) {
|
|||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
// Countdown timer (if counting down)
|
||||
if (countdownSeconds > 0) {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Timer,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "等待启动: ${formatCountdown(countdownSeconds)}",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
// Cancel button
|
||||
|
|
@ -558,6 +605,7 @@ private fun KeygenProgressScreen(
|
|||
participants: List<String>,
|
||||
currentRound: Int,
|
||||
totalRounds: Int,
|
||||
countdownSeconds: Long = -1L,
|
||||
onCancel: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -617,6 +665,46 @@ private fun KeygenProgressScreen(
|
|||
}
|
||||
}
|
||||
|
||||
// Countdown timer (if counting down - waiting for keygen to start)
|
||||
if (countdownSeconds > 0) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Timer,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "等待密钥生成启动",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
Text(
|
||||
text = formatCountdown(countdownSeconds),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onTertiaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// Participants card
|
||||
|
|
|
|||
|
|
@ -289,7 +289,13 @@ class MainViewModel @Inject constructor(
|
|||
// Setup keygen timeout callback (matching Electron's 5-minute timeout in checkAndTriggerKeygen)
|
||||
repository.setKeygenTimeoutCallback { errorMessage ->
|
||||
android.util.Log.e("MainViewModel", "Keygen timeout: $errorMessage")
|
||||
_uiState.update { it.copy(isLoading = false, error = errorMessage) }
|
||||
_uiState.update { it.copy(isLoading = false, error = errorMessage, countdownSeconds = -1L) }
|
||||
}
|
||||
|
||||
// Setup countdown tick callback for UI countdown display
|
||||
repository.setCountdownTickCallback { remainingSeconds ->
|
||||
android.util.Log.d("MainViewModel", "Countdown tick: $remainingSeconds seconds remaining")
|
||||
_uiState.update { it.copy(countdownSeconds = remainingSeconds) }
|
||||
}
|
||||
|
||||
repository.setSessionEventCallback { event ->
|
||||
|
|
@ -1217,7 +1223,10 @@ data class MainUiState(
|
|||
val error: String? = null,
|
||||
val successMessage: String? = null,
|
||||
val lastCreatedAddress: String? = null,
|
||||
val lastSignature: String? = null
|
||||
val lastSignature: String? = null,
|
||||
// 5-minute countdown for keygen/sign startup timeout
|
||||
// -1 = not counting, 0 = timeout, >0 = remaining seconds
|
||||
val countdownSeconds: Long = -1L
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue