fix(android): enable real-time progress updates for keygen/sign rounds
Connect TssNativeBridge.progress Flow to UI through: - Add progressCallback in TssRepository with startProgressCollection/stopProgressCollection - Subscribe to native bridge progress in keygen and sign methods - Add setProgressCallback in MainViewModel to update appropriate round state - Progress now flows: Go Native → TssNativeBridge → TssRepository → MainViewModel → UI 🤖 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
3576af0f25
commit
fd56de5c00
|
|
@ -67,6 +67,13 @@ class TssRepository @Inject constructor(
|
||||||
// Called every second with remaining seconds for UI countdown display
|
// Called every second with remaining seconds for UI countdown display
|
||||||
private var countdownTickCallback: ((Long) -> Unit)? = null
|
private var countdownTickCallback: ((Long) -> Unit)? = null
|
||||||
|
|
||||||
|
// Progress callback (set by ViewModel)
|
||||||
|
// Called when TSS protocol progress updates (round/totalRounds)
|
||||||
|
private var progressCallback: ((Int, Int) -> Unit)? = null
|
||||||
|
|
||||||
|
// Job for collecting progress from native bridge
|
||||||
|
private var progressCollectionJob: Job? = null
|
||||||
|
|
||||||
// Repository-level CoroutineScope for background tasks
|
// Repository-level CoroutineScope for background tasks
|
||||||
// Uses SupervisorJob so individual task failures don't cancel other tasks
|
// Uses SupervisorJob so individual task failures don't cancel other tasks
|
||||||
private val repositoryScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
private val repositoryScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
|
|
@ -294,6 +301,45 @@ class TssRepository @Inject constructor(
|
||||||
countdownTickCallback = callback
|
countdownTickCallback = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set progress callback (called by ViewModel)
|
||||||
|
* This callback is invoked when the TSS protocol progresses through rounds
|
||||||
|
* @param callback receives (currentRound, totalRounds)
|
||||||
|
*/
|
||||||
|
fun setProgressCallback(callback: (Int, Int) -> Unit) {
|
||||||
|
progressCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start collecting progress from native bridge
|
||||||
|
* Called when keygen/sign session starts
|
||||||
|
*/
|
||||||
|
private fun startProgressCollection() {
|
||||||
|
// Cancel any existing progress collection
|
||||||
|
progressCollectionJob?.cancel()
|
||||||
|
|
||||||
|
android.util.Log.d("TssRepository", "[PROGRESS] Starting progress collection from native bridge")
|
||||||
|
|
||||||
|
progressCollectionJob = repositoryScope.launch {
|
||||||
|
tssNativeBridge.progress.collect { (round, totalRounds) ->
|
||||||
|
android.util.Log.d("TssRepository", "[PROGRESS] Round $round / $totalRounds")
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
progressCallback?.invoke(round, totalRounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop collecting progress from native bridge
|
||||||
|
* Called when session ends or is cancelled
|
||||||
|
*/
|
||||||
|
private fun stopProgressCollection() {
|
||||||
|
progressCollectionJob?.cancel()
|
||||||
|
progressCollectionJob = null
|
||||||
|
android.util.Log.d("TssRepository", "[PROGRESS] Progress collection stopped")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start polling session status as fallback mechanism
|
* Start polling session status as fallback mechanism
|
||||||
* This matches Electron's checkAndTriggerKeygen() polling behavior:
|
* This matches Electron's checkAndTriggerKeygen() polling behavior:
|
||||||
|
|
@ -961,6 +1007,9 @@ class TssRepository @Inject constructor(
|
||||||
return@coroutineScope Result.failure(startResult.exceptionOrNull()!!)
|
return@coroutineScope Result.failure(startResult.exceptionOrNull()!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start collecting progress from native bridge
|
||||||
|
startProgressCollection()
|
||||||
|
|
||||||
_sessionStatus.value = SessionStatus.IN_PROGRESS
|
_sessionStatus.value = SessionStatus.IN_PROGRESS
|
||||||
|
|
||||||
// Mark ready
|
// Mark ready
|
||||||
|
|
@ -969,6 +1018,7 @@ class TssRepository @Inject constructor(
|
||||||
// Wait for keygen result
|
// Wait for keygen result
|
||||||
val keygenResult = tssNativeBridge.waitForKeygenResult(password)
|
val keygenResult = tssNativeBridge.waitForKeygenResult(password)
|
||||||
if (keygenResult.isFailure) {
|
if (keygenResult.isFailure) {
|
||||||
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.FAILED
|
_sessionStatus.value = SessionStatus.FAILED
|
||||||
return@coroutineScope Result.failure(keygenResult.exceptionOrNull()!!)
|
return@coroutineScope Result.failure(keygenResult.exceptionOrNull()!!)
|
||||||
}
|
}
|
||||||
|
|
@ -994,6 +1044,7 @@ class TssRepository @Inject constructor(
|
||||||
// Report completion
|
// Report completion
|
||||||
grpcClient.reportCompletion(sessionId, partyId, publicKeyBytes)
|
grpcClient.reportCompletion(sessionId, partyId, publicKeyBytes)
|
||||||
|
|
||||||
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.COMPLETED
|
_sessionStatus.value = SessionStatus.COMPLETED
|
||||||
pendingSessionId = null // Clear pending session ID on completion
|
pendingSessionId = null // Clear pending session ID on completion
|
||||||
|
|
||||||
|
|
@ -1003,6 +1054,7 @@ class TssRepository @Inject constructor(
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("TssRepository", "Execute keygen as joiner failed", e)
|
android.util.Log.e("TssRepository", "Execute keygen as joiner failed", e)
|
||||||
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.FAILED
|
_sessionStatus.value = SessionStatus.FAILED
|
||||||
pendingSessionId = null // Clear pending session ID on failure
|
pendingSessionId = null // Clear pending session ID on failure
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
|
|
@ -1156,12 +1208,16 @@ class TssRepository @Inject constructor(
|
||||||
return@coroutineScope Result.failure(startResult.exceptionOrNull()!!)
|
return@coroutineScope Result.failure(startResult.exceptionOrNull()!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start collecting progress from native bridge
|
||||||
|
startProgressCollection()
|
||||||
|
|
||||||
// Mark ready
|
// Mark ready
|
||||||
grpcClient.markPartyReady(sessionId, partyId)
|
grpcClient.markPartyReady(sessionId, partyId)
|
||||||
|
|
||||||
// Wait for sign result
|
// Wait for sign result
|
||||||
val signResult = tssNativeBridge.waitForSignResult()
|
val signResult = tssNativeBridge.waitForSignResult()
|
||||||
if (signResult.isFailure) {
|
if (signResult.isFailure) {
|
||||||
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.FAILED
|
_sessionStatus.value = SessionStatus.FAILED
|
||||||
return@coroutineScope Result.failure(signResult.exceptionOrNull()!!)
|
return@coroutineScope Result.failure(signResult.exceptionOrNull()!!)
|
||||||
}
|
}
|
||||||
|
|
@ -1172,6 +1228,7 @@ class TssRepository @Inject constructor(
|
||||||
val signatureBytes = android.util.Base64.decode(result.signature, android.util.Base64.NO_WRAP)
|
val signatureBytes = android.util.Base64.decode(result.signature, android.util.Base64.NO_WRAP)
|
||||||
grpcClient.reportCompletion(sessionId, partyId, signature = signatureBytes)
|
grpcClient.reportCompletion(sessionId, partyId, signature = signatureBytes)
|
||||||
|
|
||||||
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.COMPLETED
|
_sessionStatus.value = SessionStatus.COMPLETED
|
||||||
pendingSessionId = null // Clear pending session ID on completion
|
pendingSessionId = null // Clear pending session ID on completion
|
||||||
messageCollectionJob?.cancel()
|
messageCollectionJob?.cancel()
|
||||||
|
|
@ -1182,6 +1239,7 @@ class TssRepository @Inject constructor(
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("TssRepository", "Execute sign as joiner failed", e)
|
android.util.Log.e("TssRepository", "Execute sign as joiner failed", e)
|
||||||
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.FAILED
|
_sessionStatus.value = SessionStatus.FAILED
|
||||||
pendingSessionId = null // Clear pending session ID on failure
|
pendingSessionId = null // Clear pending session ID on failure
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
|
|
@ -1672,6 +1730,9 @@ class TssRepository @Inject constructor(
|
||||||
return@coroutineScope Result.failure(startResult.exceptionOrNull()!!)
|
return@coroutineScope Result.failure(startResult.exceptionOrNull()!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start collecting progress from native bridge
|
||||||
|
startProgressCollection()
|
||||||
|
|
||||||
_sessionStatus.value = SessionStatus.IN_PROGRESS
|
_sessionStatus.value = SessionStatus.IN_PROGRESS
|
||||||
|
|
||||||
// Mark ready
|
// Mark ready
|
||||||
|
|
@ -1680,6 +1741,7 @@ class TssRepository @Inject constructor(
|
||||||
// Wait for keygen result
|
// Wait for keygen result
|
||||||
val keygenResult = tssNativeBridge.waitForKeygenResult(password)
|
val keygenResult = tssNativeBridge.waitForKeygenResult(password)
|
||||||
if (keygenResult.isFailure) {
|
if (keygenResult.isFailure) {
|
||||||
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.FAILED
|
_sessionStatus.value = SessionStatus.FAILED
|
||||||
return@coroutineScope Result.failure(keygenResult.exceptionOrNull()!!)
|
return@coroutineScope Result.failure(keygenResult.exceptionOrNull()!!)
|
||||||
}
|
}
|
||||||
|
|
@ -1705,6 +1767,7 @@ class TssRepository @Inject constructor(
|
||||||
// Report completion
|
// Report completion
|
||||||
grpcClient.reportCompletion(sessionId, partyId, publicKeyBytes)
|
grpcClient.reportCompletion(sessionId, partyId, publicKeyBytes)
|
||||||
|
|
||||||
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.COMPLETED
|
_sessionStatus.value = SessionStatus.COMPLETED
|
||||||
pendingSessionId = null // Clear pending session ID on completion
|
pendingSessionId = null // Clear pending session ID on completion
|
||||||
sessionEventJob?.cancel()
|
sessionEventJob?.cancel()
|
||||||
|
|
@ -1713,6 +1776,7 @@ class TssRepository @Inject constructor(
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("TssRepository", "Start keygen as initiator failed", e)
|
android.util.Log.e("TssRepository", "Start keygen as initiator failed", e)
|
||||||
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.FAILED
|
_sessionStatus.value = SessionStatus.FAILED
|
||||||
pendingSessionId = null // Clear pending session ID on failure
|
pendingSessionId = null // Clear pending session ID on failure
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
|
|
@ -2055,6 +2119,9 @@ class TssRepository @Inject constructor(
|
||||||
return@withContext Result.failure(startResult.exceptionOrNull()!!)
|
return@withContext Result.failure(startResult.exceptionOrNull()!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start collecting progress from native bridge
|
||||||
|
startProgressCollection()
|
||||||
|
|
||||||
_sessionStatus.value = SessionStatus.IN_PROGRESS
|
_sessionStatus.value = SessionStatus.IN_PROGRESS
|
||||||
|
|
||||||
// Note: Message routing is already started in createSignSession after auto-join
|
// Note: Message routing is already started in createSignSession after auto-join
|
||||||
|
|
@ -2068,6 +2135,7 @@ class TssRepository @Inject constructor(
|
||||||
|
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.FAILED
|
_sessionStatus.value = SessionStatus.FAILED
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
}
|
}
|
||||||
|
|
@ -2082,6 +2150,7 @@ class TssRepository @Inject constructor(
|
||||||
try {
|
try {
|
||||||
val signResult = tssNativeBridge.waitForSignResult()
|
val signResult = tssNativeBridge.waitForSignResult()
|
||||||
if (signResult.isFailure) {
|
if (signResult.isFailure) {
|
||||||
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.FAILED
|
_sessionStatus.value = SessionStatus.FAILED
|
||||||
return@withContext Result.failure(signResult.exceptionOrNull()!!)
|
return@withContext Result.failure(signResult.exceptionOrNull()!!)
|
||||||
}
|
}
|
||||||
|
|
@ -2095,6 +2164,7 @@ class TssRepository @Inject constructor(
|
||||||
grpcClient.reportCompletion(session.sessionId, partyId, signature = signatureBytes)
|
grpcClient.reportCompletion(session.sessionId, partyId, signature = signatureBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.COMPLETED
|
_sessionStatus.value = SessionStatus.COMPLETED
|
||||||
messageCollectionJob?.cancel()
|
messageCollectionJob?.cancel()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -298,6 +298,30 @@ class MainViewModel @Inject constructor(
|
||||||
_uiState.update { it.copy(countdownSeconds = remainingSeconds) }
|
_uiState.update { it.copy(countdownSeconds = remainingSeconds) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup progress callback for real-time round updates from native TSS bridge
|
||||||
|
repository.setProgressCallback { round, totalRounds ->
|
||||||
|
android.util.Log.d("MainViewModel", "Progress update: $round / $totalRounds")
|
||||||
|
// Update the appropriate round state based on which session type is active
|
||||||
|
when {
|
||||||
|
// Initiator keygen (CreateWallet)
|
||||||
|
_currentSessionId.value != null && pendingJoinKeygenInfo == null && pendingJoinSignInfo == null -> {
|
||||||
|
_currentRound.value = round
|
||||||
|
}
|
||||||
|
// Joiner keygen (JoinKeygen)
|
||||||
|
pendingJoinKeygenInfo != null -> {
|
||||||
|
_joinKeygenRound.value = round
|
||||||
|
}
|
||||||
|
// Joiner sign (CoSign/参与签名)
|
||||||
|
pendingJoinSignInfo != null -> {
|
||||||
|
_coSignRound.value = round
|
||||||
|
}
|
||||||
|
// Initiator sign (Transfer)
|
||||||
|
_signSessionId.value != null -> {
|
||||||
|
_signCurrentRound.value = round
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
repository.setSessionEventCallback { event ->
|
repository.setSessionEventCallback { event ->
|
||||||
android.util.Log.d("MainViewModel", "=== MainViewModel received session event ===")
|
android.util.Log.d("MainViewModel", "=== MainViewModel received session event ===")
|
||||||
android.util.Log.d("MainViewModel", " eventType: ${event.eventType}")
|
android.util.Log.d("MainViewModel", " eventType: ${event.eventType}")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue