Merge pull request #344 from greenart7c3/claude/remove-mutable-state-FGS7i

Refactor multi-event screen state management to use centralized store
This commit is contained in:
greenart7c3
2026-03-20 10:23:38 -03:00
committed by GitHub
18 changed files with 175 additions and 167 deletions
@@ -38,6 +38,7 @@ import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.utils.Hex
import java.time.Duration
import java.time.Instant
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -110,7 +111,7 @@ class MainActivity : AppCompatActivity() {
CircularProgressIndicator()
}
} else {
val bunkerRequests = BunkerRequestUtils.state.collectAsStateWithLifecycle(emptyList())
val bunkerRequests = BunkerRequestUtils.state.collectAsStateWithLifecycle(persistentListOf())
val npub = remember { mainViewModel.getAccount(intent?.getStringExtra("current_user")) }
val accountStateViewModel: AccountStateViewModel =
@@ -16,6 +16,9 @@ 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 kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
@@ -24,21 +27,21 @@ import kotlinx.coroutines.launch
@SuppressLint("StaticFieldLeak")
class MainViewModel(val context: Context) : ViewModel() {
private val _intents = MutableStateFlow<List<IntentData>>(listOf())
private val _intents = MutableStateFlow<ImmutableList<IntentData>>(persistentListOf())
val intents = _intents.asStateFlow()
var navController: NavHostController? = null
fun addAll(list: List<IntentData>) {
val newList = list.filter { !intents.value.contains(it) }
_intents.value += newList
_intents.value = (_intents.value + newList).toPersistentList()
}
fun removeAll(intents: List<IntentData>) {
_intents.value -= intents.toSet()
_intents.value = (_intents.value - intents.toSet()).toPersistentList()
}
fun clear() {
_intents.value = emptyList()
_intents.value = persistentListOf()
}
fun getAccount(userFromIntent: String?): String? {
@@ -43,6 +43,7 @@ import com.greenart7c3.nostrsigner.ui.navigation.Route
import com.greenart7c3.nostrsigner.ui.theme.NostrSignerTheme
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.utils.Hex
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -126,7 +127,7 @@ class SignerActivity : AppCompatActivity() {
CircularProgressIndicator()
}
} else {
val bunkerRequests = BunkerRequestUtils.state.collectAsStateWithLifecycle(emptyList())
val bunkerRequests = BunkerRequestUtils.state.collectAsStateWithLifecycle(persistentListOf())
val npub = remember { mainViewModel.getAccount(intent?.getStringExtra("current_user")) }
val accountStateViewModel: AccountStateViewModel =
@@ -1,8 +1,6 @@
package com.greenart7c3.nostrsigner.models
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import com.greenart7c3.nostrsigner.ui.RememberType
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
@@ -12,6 +10,7 @@ enum class EncryptionType {
NIP04,
}
@Immutable
data class AmberBunkerRequest(
val request: BunkerRequest,
val localKey: String,
@@ -22,8 +21,6 @@ data class AmberBunkerRequest(
val name: String,
val signedEvent: Event?,
val encryptedData: EncryptedDataKind?,
val checked: MutableState<Boolean> = mutableStateOf(true),
val rememberType: MutableState<RememberType> = mutableStateOf(RememberType.NEVER),
val encryptionType: EncryptionType,
val isNostrConnectUri: Boolean,
)
@@ -1,10 +1,10 @@
package com.greenart7c3.nostrsigner.models
import androidx.compose.runtime.MutableState
import com.greenart7c3.nostrsigner.ui.RememberType
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@Immutable
data class IntentData(
val data: String,
val name: String,
@@ -16,8 +16,6 @@ data class IntentData(
val returnType: ReturnType,
val permissions: List<Permission>?,
val currentAccount: String,
val checked: MutableState<Boolean>,
val rememberType: MutableState<RememberType>,
val route: String?,
val event: Event?,
val encryptedData: EncryptedDataKind?,
@@ -28,25 +28,28 @@ import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
import com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlin.collections.toSet
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
object BunkerRequestUtils {
val state = MutableStateFlow(listOf<AmberBunkerRequest>())
val state = MutableStateFlow<ImmutableList<AmberBunkerRequest>>(persistentListOf())
fun addRequest(request: AmberBunkerRequest) {
if (state.value.any { it.request.id == request.request.id }) return
state.tryEmit(state.value + request)
state.tryEmit((state.value + request).toPersistentList())
}
fun clearRequests() {
state.tryEmit(emptyList())
state.tryEmit(persistentListOf())
}
fun remove(id: String) {
state.tryEmit(state.value.filter { it.request.id != id })
state.tryEmit(state.value.filter { it.request.id != id }.toPersistentList())
}
fun getBunkerRequests(): List<AmberBunkerRequest> = state.value
@@ -8,7 +8,6 @@ import android.net.Uri
import android.provider.Browser
import android.util.Log
import android.widget.Toast
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.Clipboard
import androidx.compose.ui.text.intl.Locale
@@ -173,8 +172,6 @@ object IntentUtils {
returnType = returnType,
permissions = listOf(),
currentAccount = Hex.decode(signedEvent.pubKey).toNpub(),
checked = mutableStateOf(true),
rememberType = mutableStateOf(RememberType.NEVER),
route = route,
event = signedEvent,
encryptedData = null,
@@ -219,8 +216,6 @@ object IntentUtils {
returnType = returnType,
permissions = listOf(),
currentAccount = Hex.decode(pubKey).toNpub(),
checked = mutableStateOf(true),
rememberType = mutableStateOf(RememberType.NEVER),
route = route,
event = null,
encryptedData = encryptedDataKind,
@@ -238,8 +233,6 @@ object IntentUtils {
returnType = returnType,
permissions = listOf(),
currentAccount = "",
checked = mutableStateOf(true),
rememberType = mutableStateOf(RememberType.NEVER),
route = route,
event = null,
encryptedData = null,
@@ -257,8 +250,6 @@ object IntentUtils {
returnType = returnType,
permissions = listOf(),
currentAccount = "",
checked = mutableStateOf(true),
rememberType = mutableStateOf(RememberType.NEVER),
route = route,
event = null,
encryptedData = null,
@@ -359,8 +350,6 @@ object IntentUtils {
returnType = returnType,
permissions = permissions?.map { Permission(it.type.trim(), it.kind, it.checked) },
currentAccount = npub ?: Hex.decode(signed.pubKey).toNpub(),
checked = mutableStateOf(true),
rememberType = mutableStateOf(RememberType.NEVER),
route = route,
event = signed,
encryptedData = null,
@@ -411,8 +400,6 @@ object IntentUtils {
returnType = returnType,
permissions = permissions?.map { Permission(it.type.trim(), it.kind, it.checked) },
currentAccount = npub ?: Hex.decode(pubKey).toNpub(),
checked = mutableStateOf(true),
rememberType = mutableStateOf(RememberType.NEVER),
route = route,
event = null,
encryptedData = encryptedDataKind,
@@ -435,8 +422,6 @@ object IntentUtils {
returnType = returnType,
permissions = permissions?.map { Permission(it.type.trim(), it.kind, it.checked) },
currentAccount = npub ?: Hex.decode(pubKey).toNpub(),
checked = mutableStateOf(true),
rememberType = mutableStateOf(RememberType.NEVER),
route = route,
event = null,
encryptedData = null,
@@ -459,8 +444,6 @@ object IntentUtils {
returnType = returnType,
permissions = permissions?.map { Permission(it.type.trim(), it.kind, it.checked) },
currentAccount = npub ?: Hex.decode(pubKey).toNpub(),
checked = mutableStateOf(true),
rememberType = mutableStateOf(RememberType.NEVER),
route = route,
event = null,
encryptedData = null,
@@ -1,10 +1,17 @@
package com.greenart7c3.nostrsigner.service
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.greenart7c3.nostrsigner.models.AmberBunkerRequest
import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.ui.RememberType
object MultiEventScreenIntents {
var intents = listOf<IntentData>()
var bunkerRequests = listOf<AmberBunkerRequest>()
var appName = ""
val checkedStates = mutableStateMapOf<String, Boolean>()
var rememberType by mutableStateOf(RememberType.NEVER)
}
@@ -38,6 +38,8 @@ import com.greenart7c3.nostrsigner.models.AmberBunkerRequest
import com.greenart7c3.nostrsigner.models.IntentResultType
import com.greenart7c3.nostrsigner.service.IntentUtils
import com.greenart7c3.nostrsigner.ui.navigation.Route
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@SuppressLint("StateFlowValueCalledInComposition", "UnrememberedMutableState")
@Composable
@@ -47,7 +49,7 @@ fun AccountScreen(
packageName: String?,
appName: String?,
mainViewModel: MainViewModel,
bunkerRequests: List<AmberBunkerRequest>,
bunkerRequests: ImmutableList<AmberBunkerRequest>,
navController: NavHostController,
isExternalRequest: Boolean = false,
) {
@@ -66,7 +68,7 @@ fun AccountScreen(
MainLoginPage(accountStateViewModel, newNavController)
}
is AccountState.LoggedIn -> {
val intents by mainViewModel.intents.collectAsState(initial = emptyList())
val intents by mainViewModel.intents.collectAsState(initial = persistentListOf())
LaunchedEffect(intent) {
intent?.let {
IntentUtils.getIntentData(
@@ -26,12 +26,13 @@ import com.greenart7c3.nostrsigner.models.IntentResultType
import com.greenart7c3.nostrsigner.ui.components.BunkerSingleEventHomeScreen
import com.greenart7c3.nostrsigner.ui.components.IntentSingleEventHomeScreen
import com.greenart7c3.nostrsigner.ui.components.MultiEventHomeScreen
import kotlinx.collections.immutable.ImmutableList
@Composable
fun IncomingRequestScreen(
modifier: Modifier,
intents: List<IntentData>,
bunkerRequests: List<AmberBunkerRequest>,
intents: ImmutableList<IntentData>,
bunkerRequests: ImmutableList<AmberBunkerRequest>,
packageName: String?,
applicationName: String?,
account: Account,
@@ -81,6 +81,7 @@ import com.greenart7c3.nostrsigner.ui.components.AmberFloatingButton
import com.greenart7c3.nostrsigner.ui.components.AmberTopAppBar
import com.greenart7c3.nostrsigner.ui.navigation.Route
import java.util.Base64
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -131,8 +132,8 @@ fun requestIgnoreBatteryOptimizations(context: Context) {
fun MainScreen(
account: Account,
accountStateViewModel: AccountStateViewModel,
intents: List<IntentData>,
bunkerRequests: List<AmberBunkerRequest>,
intents: ImmutableList<IntentData>,
bunkerRequests: ImmutableList<AmberBunkerRequest>,
packageName: String?,
appName: String?,
route: MutableState<String?>,
@@ -56,7 +56,7 @@ fun SeeDetailsScreen(
.weight(1f)
.verticalScroll(rememberScrollState()),
) {
var rememberType by remember { mutableStateOf(MultiEventScreenIntents.intents.firstOrNull()?.rememberType?.value ?: MultiEventScreenIntents.bunkerRequests.first().rememberType.value) }
var rememberType by remember { mutableStateOf(MultiEventScreenIntents.rememberType) }
val type = if (MultiEventScreenIntents.intents.isNotEmpty()) {
MultiEventScreenIntents.intents.first().type
} else {
@@ -96,15 +96,14 @@ fun SeeDetailsScreen(
onReject = {},
onChanged = {
rememberType = it
MultiEventScreenIntents.intents.forEach { intent ->
intent.rememberType.value = rememberType
}
MultiEventScreenIntents.rememberType = it
},
packageName = null,
)
}
MultiEventScreenIntents.intents.forEach { intent ->
val intentChecked = MultiEventScreenIntents.checkedStates[intent.id] ?: true
Card(
Modifier
.padding(4.dp),
@@ -118,13 +117,13 @@ fun SeeDetailsScreen(
modifier = Modifier
.fillMaxWidth()
.clickable {
intent.checked.value = !intent.checked.value
MultiEventScreenIntents.checkedStates[intent.id] = !intentChecked
},
) {
Checkbox(
checked = intent.checked.value,
checked = intentChecked,
onCheckedChange = { _ ->
intent.checked.value = !intent.checked.value
MultiEventScreenIntents.checkedStates[intent.id] = !intentChecked
},
colors = CheckboxDefaults.colors().copy(
uncheckedBorderColor = Color.Gray,
@@ -163,13 +162,14 @@ fun SeeDetailsScreen(
.weight(1f)
.padding(vertical = 8.dp),
text = data.ifBlank { message },
color = if (intent.checked.value) Color.Unspecified else Color.Gray,
color = if (intentChecked) Color.Unspecified else Color.Gray,
)
}
}
}
MultiEventScreenIntents.bunkerRequests.forEach { bunkerRequest ->
val bunkerChecked = MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] ?: true
Card(
Modifier
.padding(4.dp),
@@ -183,13 +183,13 @@ fun SeeDetailsScreen(
modifier = Modifier
.fillMaxWidth()
.clickable {
bunkerRequest.checked.value = !bunkerRequest.checked.value
MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] = !bunkerChecked
},
) {
Checkbox(
checked = bunkerRequest.checked.value,
checked = bunkerChecked,
onCheckedChange = { _ ->
bunkerRequest.checked.value = !bunkerRequest.checked.value
MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] = !bunkerChecked
},
colors = CheckboxDefaults.colors().copy(
uncheckedBorderColor = Color.Gray,
@@ -228,7 +228,7 @@ fun SeeDetailsScreen(
.weight(1f)
.padding(vertical = 8.dp),
text = data.ifBlank { message },
color = if (bunkerRequest.checked.value) Color.Unspecified else Color.Gray,
color = if (bunkerChecked) Color.Unspecified else Color.Gray,
)
}
}
@@ -50,6 +50,7 @@ import com.greenart7c3.nostrsigner.ui.navigation.Route
import com.greenart7c3.nostrsigner.ui.navigation.routes
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestConnect
import java.util.Base64
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -63,8 +64,8 @@ fun AmberTopAppBar(
context: Context,
navBackStackEntry: NavBackStackEntry?,
account: Account,
intents: List<IntentData>,
bunkerRequests: List<AmberBunkerRequest>,
intents: ImmutableList<IntentData>,
bunkerRequests: ImmutableList<AmberBunkerRequest>,
packageName: String?,
) {
if (intents.isEmpty() || packageName == null || destinationRoute != Route.IncomingRequest.route) {
@@ -9,8 +9,8 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
@@ -53,6 +53,7 @@ import com.greenart7c3.nostrsigner.service.AmberUtils
import com.greenart7c3.nostrsigner.service.ApplicationNameCache
import com.greenart7c3.nostrsigner.service.BunkerRequestUtils
import com.greenart7c3.nostrsigner.service.EventNotificationConsumer
import com.greenart7c3.nostrsigner.service.MultiEventScreenIntents
import com.greenart7c3.nostrsigner.service.model.AmberEvent
import com.greenart7c3.nostrsigner.service.toShortenHex
import com.greenart7c3.nostrsigner.ui.RememberType
@@ -63,25 +64,32 @@ import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlin.collections.forEach
import kotlin.collections.set
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable
fun BunkerMultiEventHomeScreen(
modifier: Modifier,
bunkerRequests: List<AmberBunkerRequest>,
bunkerRequests: ImmutableList<AmberBunkerRequest>,
packageName: String?,
accountParam: Account,
onLoading: (Boolean) -> Unit,
) {
val context = LocalContext.current
val hasRelayAuthEvents = bunkerRequests.any { it.request is BunkerRequestSign && (it.request as BunkerRequestSign).event.kind == 22242 }
val hasRelayAuthEvents = bunkerRequests.any { it.request is BunkerRequestSign && it.request.event.kind == 22242 }
var localAccount by remember { mutableStateOf("") }
val key = bunkerRequests.first().localKey
var rememberType by remember { mutableStateOf(RememberType.NEVER) }
var relayAuthScope by remember { mutableStateOf(RelayAuthScope.SPECIFIC) }
var appName by remember { mutableStateOf(ApplicationNameCache.names["$localAccount-$key"] ?: key.toShortenHex()) }
LaunchedEffect(Unit) {
MultiEventScreenIntents.checkedStates.clear()
MultiEventScreenIntents.rememberType = RememberType.NEVER
bunkerRequests.forEach { MultiEventScreenIntents.checkedStates[it.request.id] = true }
}
LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
localAccount = LocalPreferences.loadFromEncryptedStorage(
@@ -116,8 +124,8 @@ fun BunkerMultiEventHomeScreen(
SigningAs(accountParam)
val allCheckedState = when {
bunkerRequests.all { it.checked.value } -> ToggleableState.On
bunkerRequests.none { it.checked.value } -> ToggleableState.Off
bunkerRequests.all { MultiEventScreenIntents.checkedStates[it.request.id] ?: true } -> ToggleableState.On
bunkerRequests.none { MultiEventScreenIntents.checkedStates[it.request.id] ?: true } -> ToggleableState.Off
else -> ToggleableState.Indeterminate
}
Row(
@@ -126,26 +134,32 @@ fun BunkerMultiEventHomeScreen(
.fillMaxWidth()
.clickable {
val newValue = allCheckedState != ToggleableState.On
bunkerRequests.forEach { it.checked.value = newValue }
MultiEventScreenIntents.checkedStates.putAll(bunkerRequests.associate { it.request.id to newValue })
},
) {
TriStateCheckbox(
state = allCheckedState,
onClick = {
val newValue = allCheckedState != ToggleableState.On
bunkerRequests.forEach { it.checked.value = newValue }
MultiEventScreenIntents.checkedStates.putAll(bunkerRequests.associate { it.request.id to newValue })
},
)
Text(stringResource(R.string.select_deselect_all))
}
Column(
Modifier
.weight(1f)
.verticalScroll(rememberScrollState()),
LazyColumn(
Modifier.weight(1f),
) {
bunkerRequests.forEach { bunkerRequest ->
BunkerRequestCard(context = context, bunkerRequest = bunkerRequest)
items(bunkerRequests, key = { it.request.id }) { bunkerRequest ->
BunkerRequestCard(
context = context,
bunkerRequest = bunkerRequest,
checked = MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] ?: true,
onToggleChecked = {
val current = MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] ?: true
MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] = !current
},
)
}
}
@@ -184,9 +198,7 @@ fun BunkerMultiEventHomeScreen(
onReject = {},
onChanged = {
rememberType = it
bunkerRequests.forEach { bunkerRequest ->
bunkerRequest.rememberType.value = rememberType
}
MultiEventScreenIntents.rememberType = it
},
packageName = packageName,
)
@@ -247,7 +259,8 @@ fun BunkerMultiEventHomeScreen(
permissions = mutableListOf(),
)
if (request.rememberType.value != RememberType.NEVER && request.checked.value) {
val isChecked = MultiEventScreenIntents.checkedStates[request.request.id] ?: true
if (rememberType != RememberType.NEVER && isChecked) {
val rejectKind = if (request.request is BunkerRequestSign) request.request.event.kind else null
val rejectRelay = if (request.request is BunkerRequestSign && request.request.event.kind == 22242) {
if (relayAuthScope == RelayAuthScope.ALL) {
@@ -272,7 +285,7 @@ fun BunkerMultiEventHomeScreen(
BunkerRequestUtils.getTypeFromBunker(request.request),
rejectKind,
false,
request.rememberType.value,
rememberType,
thisAccount,
relay = rejectRelay,
)
@@ -345,10 +358,12 @@ fun BunkerMultiEventHomeScreen(
permissions = mutableListOf(),
)
val isChecked = MultiEventScreenIntents.checkedStates[request.request.id] ?: true
if (request.request is BunkerRequestSign) {
val localEvent = request.signedEvent!!
if (request.rememberType.value != RememberType.NEVER && request.checked.value) {
if (rememberType != RememberType.NEVER && isChecked) {
val signRelay = if (localEvent.kind == 22242) {
if (relayAuthScope == RelayAuthScope.ALL) {
"*"
@@ -372,7 +387,7 @@ fun BunkerMultiEventHomeScreen(
signerType = SignerType.SIGN_EVENT,
kind = localEvent.kind,
value = true,
rememberType = request.rememberType.value,
rememberType = rememberType,
account = thisAccount,
relay = signRelay,
)
@@ -387,7 +402,7 @@ fun BunkerMultiEventHomeScreen(
type = SignerType.SIGN_EVENT.toString(),
kind = localEvent.kind,
time = TimeUtils.now(),
accepted = request.checked.value,
accepted = isChecked,
content = localEvent.toJson(),
),
thisAccount.npub,
@@ -395,7 +410,7 @@ fun BunkerMultiEventHomeScreen(
BunkerRequestUtils.remove(request.request.id)
if (request.checked.value) {
if (isChecked) {
BunkerRequestUtils.sendBunkerResponse(
context,
thisAccount,
@@ -416,14 +431,14 @@ fun BunkerMultiEventHomeScreen(
)
}
} else if (request.request.method == "sign_message") {
if (request.rememberType.value != RememberType.NEVER && request.checked.value) {
if (rememberType != RememberType.NEVER && isChecked) {
AmberUtils.acceptOrRejectPermission(
application,
localKey,
SignerType.SIGN_MESSAGE,
null,
true,
request.rememberType.value,
rememberType,
thisAccount,
)
}
@@ -436,7 +451,7 @@ fun BunkerMultiEventHomeScreen(
SignerType.SIGN_MESSAGE.toString(),
null,
TimeUtils.now(),
request.checked.value,
isChecked,
content = request.request.params.first(),
),
thisAccount.npub,
@@ -445,7 +460,7 @@ fun BunkerMultiEventHomeScreen(
val signedMessage = thisAccount.signString(request.request.params.first())
BunkerRequestUtils.remove(request.request.id)
if (request.checked.value) {
if (isChecked) {
BunkerRequestUtils.sendBunkerResponse(
context,
thisAccount,
@@ -476,14 +491,14 @@ fun BunkerMultiEventHomeScreen(
SignerType.CONNECT.toString(),
null,
TimeUtils.now(),
request.checked.value,
isChecked,
content = "",
),
thisAccount.npub,
)
BunkerRequestUtils.remove(request.request.id)
if (request.checked.value) {
if (isChecked) {
BunkerRequestUtils.sendBunkerResponse(
context,
thisAccount,
@@ -506,14 +521,14 @@ fun BunkerMultiEventHomeScreen(
}
} else {
val type = BunkerRequestUtils.getTypeFromBunker(request.request)
if (request.rememberType.value != RememberType.NEVER && request.checked.value) {
if (rememberType != RememberType.NEVER && isChecked) {
AmberUtils.acceptOrRejectPermission(
application,
localKey,
type,
null,
true,
request.rememberType.value,
rememberType,
thisAccount,
)
}
@@ -527,7 +542,7 @@ fun BunkerMultiEventHomeScreen(
type.toString(),
null,
TimeUtils.now(),
request.checked.value,
isChecked,
content = if (type == SignerType.NIP04_DECRYPT || type == SignerType.NIP44_DECRYPT || type == SignerType.DECRYPT_ZAP_EVENT) {
request.encryptedData?.result ?: ""
} else {
@@ -539,7 +554,7 @@ fun BunkerMultiEventHomeScreen(
val signature = request.encryptedData?.result ?: continue
BunkerRequestUtils.remove(request.request.id)
if (request.checked.value) {
if (isChecked) {
BunkerRequestUtils.sendBunkerResponse(
context,
thisAccount,
@@ -572,7 +587,12 @@ fun BunkerMultiEventHomeScreen(
}
@Composable
private fun BunkerRequestCard(context: Context, bunkerRequest: AmberBunkerRequest) {
private fun BunkerRequestCard(
context: Context,
bunkerRequest: AmberBunkerRequest,
checked: Boolean,
onToggleChecked: () -> Unit,
) {
val type = BunkerRequestUtils.getTypeFromBunker(bunkerRequest.request)
var showDetails by remember { mutableStateOf(false) }
val hasDetails = (type == SignerType.SIGN_EVENT && bunkerRequest.signedEvent != null) ||
@@ -655,11 +675,11 @@ private fun BunkerRequestCard(context: Context, bunkerRequest: AmberBunkerReques
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable { bunkerRequest.checked.value = !bunkerRequest.checked.value },
.clickable { onToggleChecked() },
) {
Checkbox(
checked = bunkerRequest.checked.value,
onCheckedChange = { bunkerRequest.checked.value = !bunkerRequest.checked.value },
checked = checked,
onCheckedChange = { onToggleChecked() },
colors = CheckboxDefaults.colors().copy(
uncheckedBorderColor = Color.Gray,
),
@@ -671,7 +691,7 @@ private fun BunkerRequestCard(context: Context, bunkerRequest: AmberBunkerReques
) {
Text(
text = label,
color = if (bunkerRequest.checked.value) Color.Unspecified else Color.Gray,
color = if (checked) Color.Unspecified else Color.Gray,
)
if (preview.isNotBlank()) {
Text(
@@ -38,6 +38,7 @@ import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.models.PrivateZapEncryptedDataKind
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind
import com.greenart7c3.nostrsigner.service.MultiEventScreenIntents
import kotlin.collections.forEach
@Composable
@@ -66,14 +67,14 @@ fun BunkerPermissionCard(
.fillMaxWidth()
.clickable {
acceptEventsGroup[index].value = !acceptEventsGroup[index].value
item.second.forEach { it.checked.value = acceptEventsGroup[index].value }
item.second.forEach { MultiEventScreenIntents.checkedStates[it.request.id] = acceptEventsGroup[index].value }
},
) {
Checkbox(
checked = acceptEventsGroup[index].value,
onCheckedChange = { _ ->
acceptEventsGroup[index].value = !acceptEventsGroup[index].value
item.second.forEach { it.checked.value = acceptEventsGroup[index].value }
item.second.forEach { MultiEventScreenIntents.checkedStates[it.request.id] = acceptEventsGroup[index].value }
},
colors = CheckboxDefaults.colors().copy(
uncheckedBorderColor = Color.Gray,
@@ -134,7 +135,7 @@ fun BunkerPermissionCard(
)
}
if (acceptEventsGroup[index].value) {
val selected = item.second.filter { it.checked.value }.size
val selected = item.second.filter { MultiEventScreenIntents.checkedStates[it.request.id] ?: true }.size
val total = item.second.size
Row(
Modifier
@@ -11,8 +11,8 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
@@ -54,22 +54,20 @@ import com.greenart7c3.nostrsigner.models.Result
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind
import com.greenart7c3.nostrsigner.service.AmberUtils
import com.greenart7c3.nostrsigner.service.ApplicationNameCache
import com.greenart7c3.nostrsigner.service.MultiEventScreenIntents
import com.greenart7c3.nostrsigner.service.model.AmberEvent
import com.greenart7c3.nostrsigner.service.toShortenHex
import com.greenart7c3.nostrsigner.ui.RememberType
import com.greenart7c3.nostrsigner.ui.theme.orange
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlin.collections.forEach
import kotlin.collections.set
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable
fun IntentMultiEventHomeScreen(
modifier: Modifier,
intents: List<IntentData>,
intents: ImmutableList<IntentData>,
packageName: String?,
accountParam: Account,
onRemoveIntentData: (List<IntentData>, IntentResultType) -> Unit,
@@ -77,36 +75,13 @@ fun IntentMultiEventHomeScreen(
) {
val context = LocalContext.current
val hasRelayAuthEvents = intents.any { it.type == SignerType.SIGN_EVENT && it.event?.kind == 22242 }
var localAccount by remember { mutableStateOf("") }
val key = "$packageName"
var rememberType by remember { mutableStateOf(RememberType.NEVER) }
var relayAuthScope by remember { mutableStateOf(RelayAuthScope.SPECIFIC) }
LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
localAccount = LocalPreferences.loadFromEncryptedStorage(
context,
intents.firstOrNull()?.currentAccount ?: "",
)?.npub?.toShortenHex() ?: ""
}
}
var appName by remember { mutableStateOf(ApplicationNameCache.names["$localAccount-$key"] ?: key.toShortenHex()) }
LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
if (ApplicationNameCache.names["$localAccount-$key"] == null) {
val app = Amber.instance.getDatabase(accountParam.npub).dao().getByKey(key)
app?.let {
appName = it.application.name
ApplicationNameCache.names["$localAccount-$key"] = it.application.name
}
} else {
ApplicationNameCache.names["$localAccount-$key"]?.let {
appName = it
}
}
}
MultiEventScreenIntents.checkedStates.clear()
MultiEventScreenIntents.rememberType = RememberType.NEVER
intents.forEach { MultiEventScreenIntents.checkedStates[it.id] = true }
}
Column(
@@ -124,8 +99,8 @@ fun IntentMultiEventHomeScreen(
SigningAs(accountParam)
val allCheckedState = when {
intents.all { it.checked.value } -> ToggleableState.On
intents.none { it.checked.value } -> ToggleableState.Off
intents.all { MultiEventScreenIntents.checkedStates[it.id] ?: true } -> ToggleableState.On
intents.none { MultiEventScreenIntents.checkedStates[it.id] ?: true } -> ToggleableState.Off
else -> ToggleableState.Indeterminate
}
Row(
@@ -134,26 +109,32 @@ fun IntentMultiEventHomeScreen(
.fillMaxWidth()
.clickable {
val newValue = allCheckedState != ToggleableState.On
intents.forEach { it.checked.value = newValue }
MultiEventScreenIntents.checkedStates.putAll(intents.associate { it.id to newValue })
},
) {
TriStateCheckbox(
state = allCheckedState,
onClick = {
val newValue = allCheckedState != ToggleableState.On
intents.forEach { it.checked.value = newValue }
MultiEventScreenIntents.checkedStates.putAll(intents.associate { it.id to newValue })
},
)
Text(stringResource(R.string.select_deselect_all))
}
Column(
Modifier
.weight(1f)
.verticalScroll(rememberScrollState()),
LazyColumn(
Modifier.weight(1f),
) {
intents.forEach { intent ->
IntentRequestCard(context = context, intent = intent)
items(intents, key = { it.id }) { intent ->
IntentRequestCard(
context = context,
intent = intent,
checked = MultiEventScreenIntents.checkedStates[intent.id] ?: true,
onToggleChecked = {
val current = MultiEventScreenIntents.checkedStates[intent.id] ?: true
MultiEventScreenIntents.checkedStates[intent.id] = !current
},
)
}
}
@@ -192,9 +173,7 @@ fun IntentMultiEventHomeScreen(
onReject = {},
onChanged = {
rememberType = it
intents.forEach { intent ->
intent.rememberType.value = rememberType
}
MultiEventScreenIntents.rememberType = it
},
packageName = packageName,
)
@@ -250,7 +229,8 @@ fun IntentMultiEventHomeScreen(
permissions = mutableListOf(),
)
if (intentData.rememberType.value != RememberType.NEVER && intentData.checked.value) {
val isChecked = MultiEventScreenIntents.checkedStates[intentData.id] ?: true
if (rememberType != RememberType.NEVER && isChecked) {
val rejectKind = if (intentData.type == SignerType.SIGN_EVENT) intentData.event?.kind else null
val rejectRelay = if (intentData.type == SignerType.SIGN_EVENT && intentData.event?.kind == 22242) {
if (relayAuthScope == RelayAuthScope.ALL) {
@@ -275,7 +255,7 @@ fun IntentMultiEventHomeScreen(
intentData.type,
rejectKind,
false,
intentData.rememberType.value,
rememberType,
thisAccount,
relay = rejectRelay,
)
@@ -356,10 +336,12 @@ fun IntentMultiEventHomeScreen(
closeApp = false
}
val isChecked = MultiEventScreenIntents.checkedStates[intentData.id] ?: true
if (intentData.type == SignerType.SIGN_EVENT) {
val localEvent = intentData.event!!
if (intentData.rememberType.value != RememberType.NEVER && intentData.checked.value) {
if (rememberType != RememberType.NEVER && isChecked) {
val signRelay = if (localEvent.kind == 22242) {
if (relayAuthScope == RelayAuthScope.ALL) {
"*"
@@ -383,7 +365,7 @@ fun IntentMultiEventHomeScreen(
intentData.type,
localEvent.kind,
true,
intentData.rememberType.value,
rememberType,
thisAccount,
relay = signRelay,
)
@@ -398,13 +380,13 @@ fun IntentMultiEventHomeScreen(
intentData.type.toString(),
localEvent.kind,
TimeUtils.now(),
intentData.checked.value,
isChecked,
content = localEvent.toJson(),
),
thisAccount.npub,
)
if (intentData.checked.value) {
if (isChecked) {
results.add(
Result(
null,
@@ -432,14 +414,14 @@ fun IntentMultiEventHomeScreen(
)
}
} else if (intentData.type == SignerType.SIGN_MESSAGE) {
if (intentData.rememberType.value != RememberType.NEVER && intentData.checked.value) {
if (rememberType != RememberType.NEVER && isChecked) {
AmberUtils.acceptOrRejectPermission(
application,
localKey,
intentData.type,
null,
true,
intentData.rememberType.value,
rememberType,
thisAccount,
)
}
@@ -452,14 +434,14 @@ fun IntentMultiEventHomeScreen(
intentData.type.toString(),
null,
TimeUtils.now(),
intentData.checked.value,
isChecked,
content = intentData.data,
),
thisAccount.npub,
)
val signedMessage = thisAccount.signString(intentData.data)
if (intentData.checked.value) {
if (isChecked) {
results.add(
Result(
null,
@@ -471,14 +453,14 @@ fun IntentMultiEventHomeScreen(
)
}
} else {
if (intentData.rememberType.value != RememberType.NEVER && intentData.checked.value) {
if (rememberType != RememberType.NEVER && isChecked) {
AmberUtils.acceptOrRejectPermission(
application,
localKey,
intentData.type,
null,
true,
intentData.rememberType.value,
rememberType,
thisAccount,
)
}
@@ -492,7 +474,7 @@ fun IntentMultiEventHomeScreen(
intentData.type.toString(),
null,
TimeUtils.now(),
intentData.checked.value,
isChecked,
content = if (intentData.type == SignerType.NIP04_DECRYPT || intentData.type == SignerType.NIP44_DECRYPT || intentData.type == SignerType.DECRYPT_ZAP_EVENT) {
intentData.encryptedData?.result ?: ""
} else {
@@ -503,7 +485,7 @@ fun IntentMultiEventHomeScreen(
)
val signature = intentData.encryptedData?.result ?: continue
if (intentData.checked.value) {
if (isChecked) {
results.add(
Result(
null,
@@ -533,7 +515,12 @@ fun IntentMultiEventHomeScreen(
}
@Composable
private fun IntentRequestCard(context: Context, intent: IntentData) {
private fun IntentRequestCard(
context: Context,
intent: IntentData,
checked: Boolean,
onToggleChecked: () -> Unit,
) {
val type = intent.type
var showDetails by remember { mutableStateOf(false) }
val hasDetails = (type == SignerType.SIGN_EVENT && intent.event != null) ||
@@ -615,11 +602,11 @@ private fun IntentRequestCard(context: Context, intent: IntentData) {
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable { intent.checked.value = !intent.checked.value },
.clickable { onToggleChecked() },
) {
Checkbox(
checked = intent.checked.value,
onCheckedChange = { intent.checked.value = !intent.checked.value },
checked = checked,
onCheckedChange = { onToggleChecked() },
colors = CheckboxDefaults.colors().copy(
uncheckedBorderColor = Color.Gray,
),
@@ -631,7 +618,7 @@ private fun IntentRequestCard(context: Context, intent: IntentData) {
) {
Text(
text = label,
color = if (intent.checked.value) Color.Unspecified else Color.Gray,
color = if (checked) Color.Unspecified else Color.Gray,
)
if (preview.isNotBlank()) {
Text(
@@ -6,12 +6,13 @@ import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.AmberBunkerRequest
import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.models.IntentResultType
import kotlinx.collections.immutable.ImmutableList
@Composable
fun MultiEventHomeScreen(
modifier: Modifier,
intents: List<IntentData>,
bunkerRequests: List<AmberBunkerRequest>,
intents: ImmutableList<IntentData>,
bunkerRequests: ImmutableList<AmberBunkerRequest>,
packageName: String?,
accountParam: Account,
onRemoveIntentData: (List<IntentData>, IntentResultType) -> Unit,
@@ -38,6 +38,7 @@ import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.models.PrivateZapEncryptedDataKind
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind
import com.greenart7c3.nostrsigner.service.MultiEventScreenIntents
import kotlin.collections.forEach
@Composable
@@ -66,14 +67,14 @@ fun PermissionCard(
.fillMaxWidth()
.clickable {
acceptEventsGroup[index].value = !acceptEventsGroup[index].value
item.second.forEach { it.checked.value = acceptEventsGroup[index].value }
item.second.forEach { MultiEventScreenIntents.checkedStates[it.id] = acceptEventsGroup[index].value }
},
) {
Checkbox(
checked = acceptEventsGroup[index].value,
onCheckedChange = { _ ->
acceptEventsGroup[index].value = !acceptEventsGroup[index].value
item.second.forEach { it.checked.value = acceptEventsGroup[index].value }
item.second.forEach { MultiEventScreenIntents.checkedStates[it.id] = acceptEventsGroup[index].value }
},
colors = CheckboxDefaults.colors().copy(
uncheckedBorderColor = Color.Gray,
@@ -134,7 +135,7 @@ fun PermissionCard(
)
}
if (acceptEventsGroup[index].value) {
val selected = item.second.filter { it.checked.value }.size
val selected = item.second.filter { MultiEventScreenIntents.checkedStates[it.id] ?: true }.size
val total = item.second.size
Row(
Modifier