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