feat: migrate secret management to datastore proto and tink

This commit is contained in:
2026-06-02 18:15:22 +02:00
parent 2be68d38a5
commit e8b3f898e8
16 changed files with 328 additions and 128 deletions
@@ -3,7 +3,6 @@ package com.bitcointxoko.gudariwallet
import android.Manifest
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
@@ -17,6 +16,9 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.*
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import com.bitcointxoko.gudariwallet.security.EncryptedSecretStore
import com.bitcointxoko.gudariwallet.service.WalletNotificationService
import com.bitcointxoko.gudariwallet.ui.OnboardingScreen
@@ -27,6 +29,8 @@ import com.bitcointxoko.gudariwallet.ui.theme.GudariWalletTheme
import com.bitcointxoko.gudariwallet.util.NotificationHelper
import com.bitcointxoko.gudariwallet.util.IntentUtils
import kotlinx.coroutines.flow.MutableSharedFlow
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
class MainActivity : AppCompatActivity() {
@@ -57,8 +61,13 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
secretStore = EncryptedSecretStore(applicationContext)
val credentialsState = secretStore.credentials
.stateIn(
scope = lifecycleScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = null
)
// Must be first — before any notification is posted
NotificationHelper.createChannels(applicationContext)
@@ -72,7 +81,10 @@ class MainActivity : AppCompatActivity() {
setContent {
GudariWalletTheme {
var isOnboarded by remember { mutableStateOf(secretStore.isOnboarded) }
val credentials by credentialsState.collectAsState()
// Treat null (loading) the same as not-onboarded to avoid a flash
val isOnboarded = credentials?.isOnboarded == true
if (isOnboarded) {
LaunchedEffect(Unit) {
@@ -80,20 +92,20 @@ class MainActivity : AppCompatActivity() {
requestNotificationPermissionIfNeeded()
promptBatteryOptimizationIfNeeded()
}
// Pass pendingPaymentHash so WalletScreen can consume it
WalletScreen(
vm = vm,
pendingPaymentHash = pendingPaymentHash,
vm = vm,
pendingPaymentHash = pendingPaymentHash,
pendingPaymentUri = pendingPaymentUri
)
} else {
OnboardingScreen(
secretStore = secretStore,
onComplete = { isOnboarded = true }
onComplete = { /* no-op: Flow update triggers recomposition automatically */ }
)
}
}
}
}
// Called when the activity is already running and a notification is tapped