package swiss.qpq.gajumobile import android.os.Bundle import android.view.WindowManager import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.platform.LocalContext import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ProcessLifecycleOwner import swiss.qpq.gajumobile.data.AppStateManager import swiss.qpq.gajumobile.data.WalletRepository import swiss.qpq.gajumobile.data.models.Account import swiss.qpq.gajumobile.data.models.Wallet import swiss.qpq.gajumobile.security.AppLifecycleObserver import swiss.qpq.gajumobile.security.BiometricHelper import swiss.qpq.gajumobile.security.SecurityManager import swiss.qpq.gajumobile.security.KeyManager import swiss.qpq.gajumobile.security.AccountAirlock import swiss.qpq.gajumobile.ui.screens.AccountNamingScreen import swiss.qpq.gajumobile.ui.screens.CreateWalletScreen import swiss.qpq.gajumobile.ui.screens.DashboardScreen import swiss.qpq.gajumobile.ui.screens.DeleteConfirmationScreen import swiss.qpq.gajumobile.ui.screens.EnvironmentInfoScreen import swiss.qpq.gajumobile.ui.screens.ExplorerScreen import swiss.qpq.gajumobile.ui.screens.LockScreen import swiss.qpq.gajumobile.ui.screens.ManageAccountScreen import swiss.qpq.gajumobile.ui.screens.ManageWalletScreen import swiss.qpq.gajumobile.ui.screens.MnemonicRecoveryScreen import swiss.qpq.gajumobile.ui.screens.MnemonicVerifyScreen import swiss.qpq.gajumobile.ui.screens.QRReceiveScreen import swiss.qpq.gajumobile.ui.screens.QRScannerScreen import swiss.qpq.gajumobile.ui.screens.SettingsScreen import swiss.qpq.gajumobile.ui.screens.SetupChoiceScreen import swiss.qpq.gajumobile.ui.screens.ViewMnemonicScreen import swiss.qpq.gajumobile.ui.theme.MyApplicationTheme import swiss.qpq.gajumaru.core.formatting.GajuFormat import java.util.UUID class MainActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() super.onCreate(savedInstanceState) enableEdgeToEdge() window.isNavigationBarContrastEnforced = false window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver(this)) setContent { val context = LocalContext.current val isLocked by SecurityManager.isLocked.collectAsState() var lockErrorMessage by remember { mutableStateOf(null) } val walletRepo = remember { WalletRepository(context) } val appStateManager = remember { AppStateManager(context) } MyApplicationTheme { if (isLocked) { LockScreen(errorMessage = lockErrorMessage) { BiometricHelper.showBiometricPrompt( activity = this, onSuccess = { lockErrorMessage = null SecurityManager.unlock() }, onError = { err -> lockErrorMessage = err } ) } } else { val routerError: (String) -> Unit = { message -> Toast.makeText(context, message, Toast.LENGTH_LONG).show() } GajuRouter(walletRepo, appStateManager, routerError) } } } } } enum class Screen { CREATE_WALLET, SETUP_CHOICE, MNEMONIC_RECOVERY, ACCOUNT_NAMING, DASHBOARD, QR_SCANNER, QR_RECEIVE, SETTINGS, MANAGE_WALLET, MANAGE_ACCOUNT, DELETE_CONFIRM, EXPLORER, VIEW_MNEMONIC, VERIFY_MNEMONIC, ENVIRONMENT_INFO } @Composable fun GajuRouter( walletRepo: WalletRepository, appStateManager: AppStateManager, onError: (String) -> Unit ) { val context = LocalContext.current val appState by appStateManager.state.collectAsState() val initialScreen = remember { if (walletRepo.listWallets().isEmpty()) Screen.CREATE_WALLET else Screen.DASHBOARD } val screenStack = remember { mutableStateListOf(initialScreen) } val currentScreen = screenStack.last() fun navigateTo(screen: Screen) { screenStack.add(screen) } fun navigateBack() { if (screenStack.size > 1) { screenStack.removeAt(screenStack.size - 1) } } BackHandler(enabled = screenStack.size > 1) { navigateBack() } var pendingWalletName by rememberSaveable { mutableStateOf("") } var pendingAccount by remember { mutableStateOf(null) } var setupRecovered by rememberSaveable { mutableStateOf(false) } var selectedWalletId by rememberSaveable { mutableStateOf("") } var selectedAccountId by rememberSaveable { mutableStateOf("") } var deleteType by rememberSaveable { mutableStateOf("") } var deleteName by rememberSaveable { mutableStateOf("") } var viewMnemonicAccount by remember { mutableStateOf(null) } var viewMnemonicPhrase by remember { mutableStateOf?>(null) } var verifyMnemonicAccount by remember { mutableStateOf(null) } var verifyMnemonicPhrase by remember { mutableStateOf?>(null) } when (currentScreen) { Screen.CREATE_WALLET -> { CreateWalletScreen( onWalletCreated = { name -> pendingWalletName = name selectedWalletId = "" navigateTo(Screen.SETUP_CHOICE) }, onBack = if (screenStack.size > 1) { { navigateBack() } } else null ) } Screen.SETUP_CHOICE -> { SetupChoiceScreen( onCreate = { try { val masterKey = KeyManager.getMasterKey(context) val phrase = AccountAirlock.generateMnemonic() val account = AccountAirlock.createAccount("New Account", phrase, masterKey) AccountAirlock.wipePhrase(phrase) pendingAccount = account setupRecovered = false navigateTo(Screen.ACCOUNT_NAMING) } catch (e: Exception) { onError("Failed to generate account: ${e.message}") } }, onRecover = { setupRecovered = true navigateTo(Screen.MNEMONIC_RECOVERY) }, onBack = { navigateBack() } ) } Screen.MNEMONIC_RECOVERY -> { MnemonicRecoveryScreen( onMnemonicConfirmed = { phrase -> try { val masterKey = KeyManager.getMasterKey(context) val account = AccountAirlock.createAccount("Recovered Account", phrase, masterKey) AccountAirlock.wipePhrase(phrase) pendingAccount = account navigateTo(Screen.ACCOUNT_NAMING) } catch (e: Exception) { onError("Failed to recover account: ${e.message}") } }, onBack = { navigateBack() } ) } Screen.ACCOUNT_NAMING -> { val account = pendingAccount ?: return val akId = account.gajuId AccountNamingScreen( publicKey = akId, onAccountNamed = { name -> val finalAccount = account.copy(label = name) if (selectedWalletId.isNotEmpty()) { val wallet = walletRepo.loadWallet(selectedWalletId) if (wallet != null) { walletRepo.saveWallet(wallet.copy(accounts = wallet.accounts + finalAccount)) } } else { val wallet = Wallet( id = UUID.randomUUID().toString(), name = pendingWalletName.ifBlank { "My Wallet" }, accounts = listOf(finalAccount) ) walletRepo.saveWallet(wallet) } if (!setupRecovered) { appStateManager.markUnverified(finalAccount.id) } selectedWalletId = "" appStateManager.notifyWalletsChanged() screenStack.clear() screenStack.add(Screen.DASHBOARD) }, onBack = { navigateBack() } ) } Screen.VIEW_MNEMONIC -> { val acc = viewMnemonicAccount ?: return val phrase = viewMnemonicPhrase ?: return ViewMnemonicScreen(label = acc.label, phrase = phrase) { AccountAirlock.wipePhrase(phrase) viewMnemonicAccount = null viewMnemonicPhrase = null navigateBack() } } Screen.VERIFY_MNEMONIC -> { val acc = verifyMnemonicAccount ?: return val phrase = verifyMnemonicPhrase ?: return MnemonicVerifyScreen( label = acc.label, expectedPhrase = phrase, onVerified = { appStateManager.clearVerified(acc.id) AccountAirlock.wipePhrase(phrase) verifyMnemonicAccount = null verifyMnemonicPhrase = null navigateBack() }, onBack = { AccountAirlock.wipePhrase(phrase) verifyMnemonicAccount = null verifyMnemonicPhrase = null navigateBack() } ) } Screen.DELETE_CONFIRM -> { DeleteConfirmationScreen( type = deleteType, name = deleteName, onConfirm = { if (deleteType == "WALLET") { walletRepo.deleteWallet(selectedWalletId) } else { walletRepo.deleteAccount(selectedWalletId, selectedAccountId) } appStateManager.notifyWalletsChanged() screenStack.removeAt(screenStack.size - 1) screenStack.removeAt(screenStack.size - 1) }, onCancel = { navigateBack() } ) } Screen.DASHBOARD -> { val wallets = remember(appState.walletsVersion) { walletRepo.listWallets().mapNotNull { walletRepo.loadWallet(it) } } DashboardScreen( wallets = wallets, balanceFormat = GajuFormat.Type.valueOf(appState.balanceFormat), balanceUnit = GajuFormat.Unit.valueOf(appState.balanceUnit), onSend = { navigateTo(Screen.QR_SCANNER) }, onReceive = { navigateTo(Screen.QR_RECEIVE) }, onAddWallet = { selectedWalletId = "" navigateTo(Screen.CREATE_WALLET) }, onAddAccount = { walletId -> selectedWalletId = walletId navigateTo(Screen.SETUP_CHOICE) }, onAccountSettings = { walletId, accountId -> selectedWalletId = walletId selectedAccountId = accountId navigateTo(Screen.MANAGE_ACCOUNT) }, onNavigate = { dest -> val screen = when (dest) { "settings" -> Screen.SETTINGS "transactions" -> Screen.EXPLORER "environment_info" -> Screen.ENVIRONMENT_INFO else -> null } if (screen != null) navigateTo(screen) }, ) } Screen.MANAGE_WALLET -> { val wallet = remember(selectedWalletId, appState.walletsVersion) { walletRepo.loadWallet(selectedWalletId) } ?: return ManageWalletScreen( walletName = wallet.name, onRename = { newName -> walletRepo.saveWallet(wallet.copy(name = newName)) appStateManager.notifyWalletsChanged() }, onDelete = { deleteType = "WALLET" deleteName = wallet.name navigateTo(Screen.DELETE_CONFIRM) } ) } Screen.MANAGE_ACCOUNT -> { val wallet = remember(selectedWalletId, appState.walletsVersion) { walletRepo.loadWallet(selectedWalletId) } ?: return val account = wallet.accounts.find { it.id == selectedAccountId } ?: return val isUnverified = appState.unverifiedAccountIds.contains(account.id) ManageAccountScreen( account = account, walletName = wallet.name, balanceFormat = GajuFormat.Type.valueOf(appState.balanceFormat), balanceUnit = GajuFormat.Unit.valueOf(appState.balanceUnit), isUnverified = isUnverified, onViewMnemonic = { try { val masterKey = KeyManager.getMasterKey(context) val phrase = AccountAirlock.deriveMnemonic(account.privateKeyEnvelope, masterKey) viewMnemonicAccount = account viewMnemonicPhrase = phrase navigateTo(Screen.VIEW_MNEMONIC) } catch (e: Exception) { onError("Authentication failed: ${e.message}") } }, onVerifyMnemonic = { try { val masterKey = KeyManager.getMasterKey(context) val phrase = AccountAirlock.deriveMnemonic(account.privateKeyEnvelope, masterKey) verifyMnemonicAccount = account verifyMnemonicPhrase = phrase navigateTo(Screen.VERIFY_MNEMONIC) } catch (e: Exception) { onError("Authentication failed: ${e.message}") } }, onRename = { newName -> val updatedAccounts = wallet.accounts.map { if (it.id == account.id) it.copy(label = newName) else it } walletRepo.saveWallet(wallet.copy(accounts = updatedAccounts)) appStateManager.notifyWalletsChanged() }, onDelete = { deleteType = "ACCOUNT" deleteName = account.label navigateTo(Screen.DELETE_CONFIRM) } ) } Screen.SETTINGS -> { SettingsScreen( balanceFormat = GajuFormat.Type.valueOf(appState.balanceFormat), onFormatChange = { type -> appStateManager.setBalanceFormat(type.name) }, onNavigate = { dest -> val screen = when (dest) { "dashboard" -> Screen.DASHBOARD "transactions" -> Screen.EXPLORER "environment_info" -> Screen.ENVIRONMENT_INFO else -> null } if (screen != null) { screenStack.clear() screenStack.add(screen) } } ) } Screen.ENVIRONMENT_INFO -> { EnvironmentInfoScreen(onBack = { navigateBack() }) } Screen.QR_SCANNER -> QRScannerScreen(onGenerate = { navigateTo(Screen.QR_RECEIVE) }, onUpload = {}) Screen.QR_RECEIVE -> QRReceiveScreen(onDone = { navigateBack() }) Screen.EXPLORER -> ExplorerScreen(onNavigate = { dest -> val screen = when (dest) { "dashboard" -> Screen.DASHBOARD "settings" -> Screen.SETTINGS else -> null } if (screen != null) { screenStack.clear() screenStack.add(screen) } }) } }