1package com.peachpasswords.ui.pairing23import android.content.Context4import androidx.lifecycle.ViewModel5import androidx.lifecycle.viewModelScope6import com.peachpasswords.sync.CodePairingServer7import com.peachpasswords.sync.PairingEvent8import com.peachpasswords.sync.PairingSession9import com.peachpasswords.sync.PeachSyncService10import com.peachpasswords.sync.buildLocalIdentity11import com.peachpasswords.sync.deriveLocalSyncKeypair12import com.peachpasswords.sync.getOrCreateDeviceId13import com.peachpasswords.vault.VaultCrypto14import com.peachpasswords.vault.VaultRepository15import dagger.hilt.android.lifecycle.HiltViewModel16import dagger.hilt.android.qualifiers.ApplicationContext17import kotlinx.coroutines.flow.MutableStateFlow18import kotlinx.coroutines.flow.StateFlow19import kotlinx.coroutines.flow.asStateFlow20import kotlinx.coroutines.launch21import javax.inject.Inject222324252627282930sealed interface CodePairUiState {31 data object Starting : CodePairUiState32 data class ShowingCode(val code: String, val ip: String, val port: Int) : CodePairUiState33 data class ConfirmSas(val sas: String, val deviceName: String, val session: PairingSession) : CodePairUiState34 data class Paired(val deviceName: String) : CodePairUiState35 data class Failed(val reason: String) : CodePairUiState36}3738@HiltViewModel39class CodePairViewModel @Inject constructor(40 private val vault: VaultRepository,41 private val server: CodePairingServer,42 @ApplicationContext private val appContext: Context,43) : ViewModel() {4445 private val _state = MutableStateFlow<CodePairUiState>(CodePairUiState.Starting)46 val state: StateFlow<CodePairUiState> = _state.asStateFlow()4748 fun start() {49 val master = vault.syncMasterKey() ?: run {50 _state.value = CodePairUiState.Failed("vault_locked")51 return52 }53 PeachSyncService.start(appContext)5455 val keys = deriveLocalSyncKeypair(master, getOrCreateDeviceId(appContext))56 val identity = buildLocalIdentity(appContext, master)57 VaultCrypto.zero(master)5859 60 61 62 server.confirmationSink = { pending ->63 _state.value = CodePairUiState.ConfirmSas(pending.sas, pending.deviceName, pending.session)64 }6566 viewModelScope.launch {67 server.start(viewModelScope, keys.secretKey, keys.publicKey, identity).collect { event ->68 _state.value = when (event) {69 is PairingEvent.Listening -> CodePairUiState.ShowingCode(event.code, event.ip, event.port)70 is PairingEvent.Paired -> CodePairUiState.Paired(event.deviceName)71 is PairingEvent.Rejected -> CodePairUiState.Failed(event.reason)72 73 is PairingEvent.AwaitingConfirmation -> _state.value74 }75 }76 }77 }7879 fun confirmSas(matches: Boolean) {80 (_state.value as? CodePairUiState.ConfirmSas)?.session?.confirm(matches)81 }8283 fun stop() {84 server.confirmationSink = null85 server.stop()86 }8788 override fun onCleared() {89 stop()90 super.onCleared()91 }92}