Files
Amber/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt
T
Claude 9b65375138 Convert legacy Long timing overloads to Duration, silence flavor warnings
Replace every Long-millisecond call to delay/withTimeoutOrNull/debounce
with the kotlin.time.Duration overloads, converting the related
constants (EOSE/backup/check timeouts, clipboard clear delay) and the
retryWithBackoff/reconnect backoff state to Duration as well.

Also suppress the three deliberate cross-flavor compiler warnings in
TrustScoreService, WebDavService and ZapstoreUpdater: the offline
flavor compiles against OkHttp 4 where response.body is nullable, so
the safe call/elvis is required there and only redundant in the free
flavor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho6LW6eEBkVnnpFvr5tvpi
2026-07-08 21:05:34 +00:00

170 lines
7.2 KiB
Kotlin

package com.greenart7c3.nostrsigner
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import androidx.compose.runtime.Stable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import com.greenart7c3.nostrsigner.service.BunkerRequestUtils
import com.greenart7c3.nostrsigner.service.IntentUtils
import com.greenart7c3.nostrsigner.ui.AccountStateViewModel
import com.greenart7c3.nostrsigner.ui.navigation.Route
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.utils.Hex
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Stable
@SuppressLint("StaticFieldLeak")
class MainViewModel(val context: Context) : ViewModel() {
var navController: NavHostController? = null
// Intent IDs added by this ViewModel instance — used by onDestroy to remove only
// the intents owned by this activity, avoiding races with a new SignerActivity
// instance that may have already added its own intents.
val addedIntentIds = mutableSetOf<String>()
fun getAccount(userFromIntent: String?): String? {
val currentAccount = LocalPreferences.currentAccount(context)
try {
if (!userFromIntent.isNullOrBlank()) {
if (userFromIntent.startsWith("npub")) {
if (LocalPreferences.containsAccount(context, userFromIntent)) {
AmberLog.d(Amber.TAG, "getAccount: $userFromIntent")
return userFromIntent
}
} else {
val localNpub = Hex.decode(userFromIntent).toNpub()
if (LocalPreferences.containsAccount(context, localNpub)) {
AmberLog.d(Amber.TAG, "getAccount: $localNpub")
return localNpub
}
}
}
val pubKeys =
IntentUtils.intents.value.mapNotNull {
it.event?.pubKey
}.filter { it.isNotBlank() } + BunkerRequestUtils.getBunkerRequests().mapNotNull {
when (val parsed = Nip19Parser.uriToRoute(it.currentAccount)?.entity) {
is NPub -> parsed.hex
else -> null
}
}
if (pubKeys.isEmpty()) {
if (currentAccount != null && LocalPreferences.containsAccount(context, currentAccount)) {
AmberLog.d(Amber.TAG, "getAccount: $currentAccount")
return currentAccount
}
val acc = LocalPreferences.allSavedAccounts(context).firstOrNull()
if (acc != null) {
AmberLog.d(Amber.TAG, "getAccount: ${acc.npub}")
return acc.npub
} else {
AmberLog.d(Amber.TAG, "getAccount: null")
return null
}
}
val npub = Hex.decode(pubKeys.first()).toNpub()
AmberLog.d(Amber.TAG, "getAccount: $npub")
return npub
} catch (e: Exception) {
AmberLog.e(Amber.TAG, "Error getting account", e)
if (currentAccount != null && LocalPreferences.containsAccount(context, currentAccount)) {
AmberLog.d(Amber.TAG, "getAccount: $currentAccount")
return currentAccount
}
val acc = LocalPreferences.allSavedAccounts(context).firstOrNull()
if (acc != null) {
AmberLog.d(Amber.TAG, "getAccount: ${acc.npub}")
return acc.npub
} else {
AmberLog.d(Amber.TAG, "getAccount: null")
return null
}
}
}
fun showBunkerRequests(accountStateViewModel: AccountStateViewModel? = null) {
val requests =
BunkerRequestUtils.getBunkerRequests().map {
it.copy()
}
viewModelScope.launch(Dispatchers.IO) {
if (requests.isNotEmpty()) {
val npub = getAccount(null)
val currentAccount = LocalPreferences.currentAccount(context)
if (currentAccount != null && npub != null && currentAccount != npub && npub.isNotBlank()) {
if (npub.startsWith("npub")) {
AmberLog.d(Amber.TAG, "Switching account to $npub")
if (LocalPreferences.containsAccount(context, npub)) {
accountStateViewModel?.switchUser(npub, Route.IncomingRequest.route)
}
} else {
val localNpub = Hex.decode(npub).toNpub()
AmberLog.d(Amber.TAG, "Switching account to $localNpub")
if (LocalPreferences.containsAccount(context, localNpub)) {
accountStateViewModel?.switchUser(localNpub, Route.IncomingRequest.route)
}
}
}
}
}
}
fun onNewIntent(
intent: Intent,
callingPackage: String?,
) {
viewModelScope.launch(Dispatchers.IO) {
val account = LocalPreferences.loadFromEncryptedStorage(context)
account?.let { acc ->
val intentData = IntentUtils.getIntentData(context, intent, callingPackage, intent.getStringExtra("route"), acc)
if (intentData != null) {
IntentUtils.addAll(listOf(intentData))
addedIntentIds.add(intentData.id)
// The calling app is visible to us now; capture its icon/name.
IntentUtils.persistNativeAppMetadata(context, acc, callingPackage)
}
intent.getStringExtra("route")?.let { route ->
viewModelScope.launch(Dispatchers.Main) {
var error = true
var count = 0
while (error && count < 10) {
delay(100.milliseconds)
count++
try {
if (route == Route.UpdateSettings.route) {
navController?.navigate(Route.Settings.route) {
popUpTo(0)
}
navController?.navigate(Route.UpdateSettings.route)
} else {
navController?.navigate(route) {
popUpTo(0)
}
}
error = false
} catch (e: Exception) {
AmberLog.e(Amber.TAG, "Error navigating to $route", e)
}
}
}
}
}
}
}
}