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 import android.provider.Settings import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.* import androidx.core.content.edit import androidx.core.net.toUri import com.bitcointxoko.gudariwallet.security.EncryptedSecretStore import com.bitcointxoko.gudariwallet.service.WalletNotificationService import com.bitcointxoko.gudariwallet.ui.OnboardingScreen import com.bitcointxoko.gudariwallet.ui.WalletScreen import com.bitcointxoko.gudariwallet.ui.WalletViewModel import com.bitcointxoko.gudariwallet.ui.WalletViewModelFactory import com.bitcointxoko.gudariwallet.ui.theme.GudariWalletTheme import com.bitcointxoko.gudariwallet.util.NotificationHelper import com.bitcointxoko.gudariwallet.util.IntentUtils import kotlinx.coroutines.flow.MutableSharedFlow class MainActivity : AppCompatActivity() { private lateinit var secretStore: EncryptedSecretStore private val vm: WalletViewModel by viewModels { WalletViewModelFactory(application) } // Holds a paymentHash that arrived via notification tap. // WalletScreen observes this and navigates to the detail screen. // MutableState so Compose recomposes when it changes. private val pendingPaymentHash = mutableStateOf(null) // Holds a payment URI that arrived via deep link / intent (BTCPay, external apps). private val pendingPaymentUri = mutableStateOf(null) private val requestNotificationPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { /* informational */ } // Broadcasts window focus gains to any observer in the composition val windowFocusEvents = MutableSharedFlow(extraBufferCapacity = 1) override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) windowFocusEvents.tryEmit(Unit) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) secretStore = EncryptedSecretStore(applicationContext) // Must be first — before any notification is posted NotificationHelper.createChannels(applicationContext) // Read hash from the intent that launched the activity (cold start from notification) pendingPaymentHash.value = intent.getStringExtra(NotificationHelper.EXTRA_PAYMENT_HASH) // Read payment URI from the intent that launched the activity (cold start from deep link) pendingPaymentUri.value = IntentUtils.extractPaymentUri(intent) enableEdgeToEdge() setContent { GudariWalletTheme { var isOnboarded by remember { mutableStateOf(secretStore.isOnboarded) } if (isOnboarded) { LaunchedEffect(Unit) { WalletNotificationService.start(this@MainActivity) requestNotificationPermissionIfNeeded() promptBatteryOptimizationIfNeeded() } // Pass pendingPaymentHash so WalletScreen can consume it WalletScreen( vm = vm, pendingPaymentHash = pendingPaymentHash, pendingPaymentUri = pendingPaymentUri ) } else { OnboardingScreen( secretStore = secretStore, onComplete = { isOnboarded = true } ) } } } } // Called when the activity is already running and a notification is tapped // (FLAG_ACTIVITY_SINGLE_TOP prevents a new instance being created) override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) val hash = intent.getStringExtra(NotificationHelper.EXTRA_PAYMENT_HASH) if (hash != null) { pendingPaymentHash.value = hash } val uri = IntentUtils.extractPaymentUri(intent) if (uri != null) { pendingPaymentUri.value = uri } } // ── Permission / battery helpers (unchanged) ────────────────────────────── private fun requestNotificationPermissionIfNeeded() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { requestNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS) } } private fun promptBatteryOptimizationIfNeeded() { val pm = getSystemService(POWER_SERVICE) as PowerManager if (pm.isIgnoringBatteryOptimizations(packageName)) return val prefs = getSharedPreferences("gudari_ui_prefs", MODE_PRIVATE) if (prefs.getBoolean("battery_opt_prompted", false)) return prefs.edit { putBoolean("battery_opt_prompted", true) } AlertDialog.Builder(this) .setTitle("Enable background notifications") .setMessage( "To receive payment alerts when the app is closed, allow Gudari Wallet " + "to run without battery restrictions.\n\n" + "Tap 'Open Settings', then select Battery → Unrestricted." ) .setPositiveButton("Open Settings") { _, _ -> openBatterySettings() } .setNegativeButton("Not now", null) .show() } private fun openBatterySettings() { val appDetailsIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { data = "package:$packageName".toUri() } if (tryStartActivity(appDetailsIntent)) return tryStartActivity(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)) } private fun tryStartActivity(intent: Intent): Boolean { return try { startActivity(intent); true } catch (e: ActivityNotFoundException) { false } } }