mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 19:08:23 +00:00
Merge #7811888b: Redesign multi-request approval screen with Approve/De…
Redesign multi-request approval screen with Approve/Deny toggles (#497) nostr:nevent1qqs8syvg3ddd7l3cwmfcuwy8f6hdags9usa706kprs506fm9cxsujjcpz3mhxue69uhhyetvv9ujumn8d96zuer9wc06qjx5 PR-Author: greenart7c3 nostr:npub1w4uswmv6lu9yel005l3qgheysmr7tk9uvwluddznju3nuxalevvs2d0jr5 PR description: Replace the confusing "select + Approve/Discard" interaction on the multi-event approval screen with an explicit Approve/Deny model: - Each request card and each group header now has an Approve/Deny segmented toggle (approve = primary, deny = error). - Removed the top "Approve/Deny all" toggle and the per-group TriStateCheckbox; group headers carry their own Approve/Deny toggle. - Replaced the two bottom buttons (Approve selected / Discard selected) with a single Confirm button. Confirm commits every per-request decision: approve signs and (when remembered) persists an accept rule; deny rejects and (when remembered) persists a deny rule, preserving existing deny-always behavior. - Bunker path now sends a proper bunker error response for denied requests instead of leaving the client to time out. - Relabeled the per-group remember control to "Remember my choice for" since it now covers both approve and deny rule persistence. - Fixed deny toggle contrast: selected text now uses onError for the deny segment instead of hardcoded black on dark-red. AmberToggles gains an optional indicatorColor and selectedTextColor; ToggleOption takes a selectedTextColor param. Removed unused discard_all/approve_all strings across all locales. Closes #497
This commit is contained in:
@@ -23,6 +23,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -47,6 +48,8 @@ fun <T> AmberToggles(
|
|||||||
onSelected: (T) -> Unit,
|
onSelected: (T) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
label: @Composable (T) -> String,
|
label: @Composable (T) -> String,
|
||||||
|
indicatorColor: @Composable (T) -> Color = { MaterialTheme.colorScheme.primary },
|
||||||
|
selectedTextColor: @Composable (T) -> Color = { Color.Black },
|
||||||
) {
|
) {
|
||||||
val count = options.size.coerceAtLeast(1)
|
val count = options.size.coerceAtLeast(1)
|
||||||
val selectedIndex = options.indexOf(selected).coerceAtLeast(0)
|
val selectedIndex = options.indexOf(selected).coerceAtLeast(0)
|
||||||
@@ -100,7 +103,7 @@ fun <T> AmberToggles(
|
|||||||
.width(segmentWidth)
|
.width(segmentWidth)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.clip(RoundedCornerShape(4.dp))
|
.clip(RoundedCornerShape(4.dp))
|
||||||
.background(MaterialTheme.colorScheme.primary),
|
.background(indicatorColor(selected)),
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(modifier = Modifier.fillMaxHeight()) {
|
Row(modifier = Modifier.fillMaxHeight()) {
|
||||||
@@ -109,6 +112,7 @@ fun <T> AmberToggles(
|
|||||||
modifier = Modifier.width(segmentWidth),
|
modifier = Modifier.width(segmentWidth),
|
||||||
text = label(option),
|
text = label(option),
|
||||||
isSelected = option == selected,
|
isSelected = option == selected,
|
||||||
|
selectedTextColor = selectedTextColor(option),
|
||||||
onClick = { onSelected(option) },
|
onClick = { onSelected(option) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+199
-332
@@ -2,22 +2,16 @@ package com.greenart7c3.nostrsigner.ui.components
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
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.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
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
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.CheckboxDefaults
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TriStateCheckbox
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -30,7 +24,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.state.ToggleableState
|
|
||||||
import androidx.compose.ui.text.capitalize
|
import androidx.compose.ui.text.capitalize
|
||||||
import androidx.compose.ui.text.intl.Locale
|
import androidx.compose.ui.text.intl.Locale
|
||||||
import androidx.compose.ui.text.toLowerCase
|
import androidx.compose.ui.text.toLowerCase
|
||||||
@@ -58,7 +51,6 @@ import com.greenart7c3.nostrsigner.service.RelayUrlUtils
|
|||||||
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
|
||||||
import com.greenart7c3.nostrsigner.ui.theme.orange
|
|
||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestConnect
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestConnect
|
||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestSign
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestSign
|
||||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
||||||
@@ -89,6 +81,7 @@ fun BunkerMultiEventHomeScreen(
|
|||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
MultiEventScreenIntents.checkedStates.clear()
|
MultiEventScreenIntents.checkedStates.clear()
|
||||||
MultiEventScreenIntents.rememberType = RememberType.NEVER
|
MultiEventScreenIntents.rememberType = RememberType.NEVER
|
||||||
|
// checkedStates now holds the per-request decision: true = Approve, false = Deny.
|
||||||
bunkerRequests.forEach { MultiEventScreenIntents.checkedStates[it.request.id] = true }
|
bunkerRequests.forEach { MultiEventScreenIntents.checkedStates[it.request.id] = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,30 +121,6 @@ fun BunkerMultiEventHomeScreen(
|
|||||||
|
|
||||||
SigningAs(accountParam)
|
SigningAs(accountParam)
|
||||||
|
|
||||||
val allCheckedState = when {
|
|
||||||
bunkerRequests.all { MultiEventScreenIntents.checkedStates[it.request.id] ?: true } -> ToggleableState.On
|
|
||||||
bunkerRequests.none { MultiEventScreenIntents.checkedStates[it.request.id] ?: true } -> ToggleableState.Off
|
|
||||||
else -> ToggleableState.Indeterminate
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable {
|
|
||||||
val newValue = allCheckedState != ToggleableState.On
|
|
||||||
MultiEventScreenIntents.checkedStates.putAll(bunkerRequests.associate { it.request.id to newValue })
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
TriStateCheckbox(
|
|
||||||
state = allCheckedState,
|
|
||||||
onClick = {
|
|
||||||
val newValue = allCheckedState != ToggleableState.On
|
|
||||||
MultiEventScreenIntents.checkedStates.putAll(bunkerRequests.associate { it.request.id to newValue })
|
|
||||||
},
|
|
||||||
)
|
|
||||||
Text(stringResource(R.string.select_deselect_all))
|
|
||||||
}
|
|
||||||
|
|
||||||
val groups = remember(bunkerRequests) {
|
val groups = remember(bunkerRequests) {
|
||||||
groupRequests(bunkerRequests) {
|
groupRequests(bunkerRequests) {
|
||||||
requestGroupKey(
|
requestGroupKey(
|
||||||
@@ -170,19 +139,14 @@ fun BunkerMultiEventHomeScreen(
|
|||||||
val expanded = groups.size == 1 || (expandedGroups[groupKey] ?: false)
|
val expanded = groups.size == 1 || (expandedGroups[groupKey] ?: false)
|
||||||
if (groups.size > 1) {
|
if (groups.size > 1) {
|
||||||
item(key = "group-header:${groupKey.type.name}:${groupKey.payload?.name ?: ""}:${groupKey.kind ?: ""}") {
|
item(key = "group-header:${groupKey.type.name}:${groupKey.payload?.name ?: ""}:${groupKey.kind ?: ""}") {
|
||||||
val groupState = when {
|
val groupApproved = groupItems.all { MultiEventScreenIntents.checkedStates[it.request.id] ?: true }
|
||||||
groupItems.all { MultiEventScreenIntents.checkedStates[it.request.id] ?: true } -> ToggleableState.On
|
|
||||||
groupItems.none { MultiEventScreenIntents.checkedStates[it.request.id] ?: true } -> ToggleableState.Off
|
|
||||||
else -> ToggleableState.Indeterminate
|
|
||||||
}
|
|
||||||
RequestGroupHeader(
|
RequestGroupHeader(
|
||||||
label = groupKey.toLabel(context),
|
label = groupKey.toLabel(context),
|
||||||
count = groupItems.size,
|
count = groupItems.size,
|
||||||
state = groupState,
|
approved = groupApproved,
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
onToggle = {
|
onApproveChanged = { approve ->
|
||||||
val newValue = groupState != ToggleableState.On
|
MultiEventScreenIntents.checkedStates.putAll(groupItems.associate { it.request.id to approve })
|
||||||
MultiEventScreenIntents.checkedStates.putAll(groupItems.associate { it.request.id to newValue })
|
|
||||||
},
|
},
|
||||||
onExpandToggle = {
|
onExpandToggle = {
|
||||||
expandedGroups[groupKey] = !(expandedGroups[groupKey] ?: false)
|
expandedGroups[groupKey] = !(expandedGroups[groupKey] ?: false)
|
||||||
@@ -208,10 +172,9 @@ fun BunkerMultiEventHomeScreen(
|
|||||||
BunkerRequestCard(
|
BunkerRequestCard(
|
||||||
context = context,
|
context = context,
|
||||||
bunkerRequest = bunkerRequest,
|
bunkerRequest = bunkerRequest,
|
||||||
checked = MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] ?: true,
|
approved = MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] ?: true,
|
||||||
onToggleChecked = {
|
onApproveChanged = {
|
||||||
val current = MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] ?: true
|
MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] = it
|
||||||
MultiEventScreenIntents.checkedStates[bunkerRequest.request.id] = !current
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -219,21 +182,20 @@ fun BunkerMultiEventHomeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
AmberButton(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 16.dp),
|
.padding(vertical = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
text = stringResource(R.string.confirm),
|
||||||
) {
|
onClick = {
|
||||||
AmberButton(
|
onLoading(true)
|
||||||
Modifier.weight(1f),
|
Amber.instance.applicationIOScope.launch(Dispatchers.IO) {
|
||||||
colors = ButtonDefaults.buttonColors().copy(
|
try {
|
||||||
containerColor = orange,
|
reconnectToRelays()
|
||||||
),
|
val closeApp = bunkerRequests.any { it.closeApplication }
|
||||||
onClick = {
|
|
||||||
Amber.instance.applicationIOScope.launch {
|
|
||||||
var closeApp = true
|
|
||||||
BunkerRequestUtils.clearRequests()
|
BunkerRequestUtils.clearRequests()
|
||||||
|
EventNotificationConsumer(context).notificationManager().cancelAll()
|
||||||
|
finishActivity(closeApp)
|
||||||
for (request in bunkerRequests) {
|
for (request in bunkerRequests) {
|
||||||
val thisAccount =
|
val thisAccount =
|
||||||
if (request.currentAccount.isNotBlank()) {
|
if (request.currentAccount.isNotBlank()) {
|
||||||
@@ -247,20 +209,23 @@ fun BunkerMultiEventHomeScreen(
|
|||||||
|
|
||||||
val localKey = request.localKey
|
val localKey = request.localKey
|
||||||
val dao = Amber.instance.dao(thisAccount.npub)
|
val dao = Amber.instance.dao(thisAccount.npub)
|
||||||
|
val historyDatabase = Amber.instance.getHistoryDatabase(thisAccount.npub)
|
||||||
|
val savedApplication = dao.getByKey(localKey)
|
||||||
|
|
||||||
val secret = if (request.request is BunkerRequestConnect) {
|
val secret = if (request.request is BunkerRequestConnect) {
|
||||||
request.request.secret ?: ""
|
request.request.secret ?: ""
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
val application =
|
val application =
|
||||||
dao
|
savedApplication ?: ApplicationWithPermissions(
|
||||||
.getByKey(localKey) ?: ApplicationWithPermissions(
|
|
||||||
application = ApplicationEntity(
|
application = ApplicationEntity(
|
||||||
localKey,
|
localKey,
|
||||||
"",
|
request.clientMetadata?.name ?: "",
|
||||||
listOf(),
|
listOf(),
|
||||||
"",
|
request.clientMetadata?.url ?: "",
|
||||||
"",
|
request.clientMetadata?.image ?: "",
|
||||||
"",
|
"",
|
||||||
thisAccount.hexKey,
|
thisAccount.hexKey,
|
||||||
true,
|
true,
|
||||||
@@ -274,7 +239,7 @@ fun BunkerMultiEventHomeScreen(
|
|||||||
permissions = mutableListOf(),
|
permissions = mutableListOf(),
|
||||||
)
|
)
|
||||||
|
|
||||||
val isChecked = MultiEventScreenIntents.checkedStates[request.request.id] ?: true
|
val isApproved = MultiEventScreenIntents.checkedStates[request.request.id] ?: true
|
||||||
val requestType = BunkerRequestUtils.getTypeFromBunker(request.request)
|
val requestType = BunkerRequestUtils.getTypeFromBunker(request.request)
|
||||||
val groupKey = requestGroupKey(
|
val groupKey = requestGroupKey(
|
||||||
type = requestType,
|
type = requestType,
|
||||||
@@ -283,165 +248,98 @@ fun BunkerMultiEventHomeScreen(
|
|||||||
nip44v3Kind = BunkerRequestUtils.getNip44v3Kind(request.request),
|
nip44v3Kind = BunkerRequestUtils.getNip44v3Kind(request.request),
|
||||||
)
|
)
|
||||||
val rememberType = groupRememberTypes[groupKey] ?: RememberType.NEVER
|
val rememberType = groupRememberTypes[groupKey] ?: RememberType.NEVER
|
||||||
if (rememberType != RememberType.NEVER && isChecked) {
|
|
||||||
val decryptTypeScope = groupDecryptScopes[groupKey] ?: defaultDecryptTypeScope(requestType)
|
|
||||||
val rejectKind = when {
|
|
||||||
request.request is BunkerRequestSign -> request.request.event.kind
|
|
||||||
requestType == SignerType.NIP44_V3_ENCRYPT || requestType == SignerType.NIP44_V3_DECRYPT ->
|
|
||||||
if (decryptTypeScope == DecryptTypeScope.SPECIFIC) BunkerRequestUtils.getNip44v3Kind(request.request) else null
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
val rejectRelay = if (request.request is BunkerRequestSign && request.request.event.kind == 22242) {
|
|
||||||
if ((groupRelayAuthScopes[groupKey] ?: RelayAuthScope.SPECIFIC) == RelayAuthScope.ALL) {
|
|
||||||
"*"
|
|
||||||
} else {
|
|
||||||
RelayUrlUtils.extractHostAndPort(AmberEvent.relay(request.request.event))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
AmberUtils.acceptOrRejectPermission(
|
|
||||||
application,
|
|
||||||
localKey,
|
|
||||||
requestType,
|
|
||||||
rejectKind,
|
|
||||||
false,
|
|
||||||
rememberType,
|
|
||||||
thisAccount,
|
|
||||||
relay = rejectRelay,
|
|
||||||
encryptedData = request.encryptedData,
|
|
||||||
decryptTypeScope = decryptTypeScope,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!application.application.closeApplication) {
|
if (request.request is BunkerRequestSign) {
|
||||||
closeApp = false
|
val localEvent = request.signedEvent!!
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EventNotificationConsumer(context).notificationManager().cancelAll()
|
if (rememberType != RememberType.NEVER) {
|
||||||
finishActivity(closeApp)
|
val signRelay = if (localEvent.kind == 22242) {
|
||||||
}
|
if ((groupRelayAuthScopes[groupKey] ?: RelayAuthScope.SPECIFIC) == RelayAuthScope.ALL) {
|
||||||
},
|
"*"
|
||||||
text = stringResource(R.string.discard_all),
|
|
||||||
)
|
|
||||||
|
|
||||||
AmberButton(
|
|
||||||
Modifier.weight(1f),
|
|
||||||
text = stringResource(R.string.approve_all),
|
|
||||||
onClick = {
|
|
||||||
onLoading(true)
|
|
||||||
Amber.instance.applicationIOScope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
reconnectToRelays()
|
|
||||||
val closeApp = bunkerRequests.any { it.closeApplication }
|
|
||||||
BunkerRequestUtils.clearRequests()
|
|
||||||
EventNotificationConsumer(context).notificationManager().cancelAll()
|
|
||||||
finishActivity(closeApp)
|
|
||||||
for (request in bunkerRequests) {
|
|
||||||
val thisAccount =
|
|
||||||
if (request.currentAccount.isNotBlank()) {
|
|
||||||
LocalPreferences.loadFromEncryptedStorage(
|
|
||||||
context,
|
|
||||||
request.currentAccount,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
accountParam
|
|
||||||
} ?: continue
|
|
||||||
|
|
||||||
val localKey = request.localKey
|
|
||||||
val dao = Amber.instance.dao(thisAccount.npub)
|
|
||||||
val historyDatabase = Amber.instance.getHistoryDatabase(thisAccount.npub)
|
|
||||||
val savedApplication = dao.getByKey(localKey)
|
|
||||||
|
|
||||||
val secret = if (request.request is BunkerRequestConnect) {
|
|
||||||
request.request.secret ?: ""
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
val application =
|
|
||||||
savedApplication ?: ApplicationWithPermissions(
|
|
||||||
application = ApplicationEntity(
|
|
||||||
localKey,
|
|
||||||
request.clientMetadata?.name ?: "",
|
|
||||||
listOf(),
|
|
||||||
request.clientMetadata?.url ?: "",
|
|
||||||
request.clientMetadata?.image ?: "",
|
|
||||||
"",
|
|
||||||
thisAccount.hexKey,
|
|
||||||
true,
|
|
||||||
secret,
|
|
||||||
secret.isNotBlank(),
|
|
||||||
thisAccount.signPolicy,
|
|
||||||
request.closeApplication,
|
|
||||||
0L,
|
|
||||||
lastUsed = TimeUtils.now(),
|
|
||||||
),
|
|
||||||
permissions = mutableListOf(),
|
|
||||||
)
|
|
||||||
|
|
||||||
val isChecked = MultiEventScreenIntents.checkedStates[request.request.id] ?: true
|
|
||||||
val requestType = BunkerRequestUtils.getTypeFromBunker(request.request)
|
|
||||||
val groupKey = requestGroupKey(
|
|
||||||
type = requestType,
|
|
||||||
eventKind = (request.request as? BunkerRequestSign)?.event?.kind,
|
|
||||||
encryptedData = request.encryptedData,
|
|
||||||
nip44v3Kind = BunkerRequestUtils.getNip44v3Kind(request.request),
|
|
||||||
)
|
|
||||||
val rememberType = groupRememberTypes[groupKey] ?: RememberType.NEVER
|
|
||||||
|
|
||||||
if (request.request is BunkerRequestSign) {
|
|
||||||
val localEvent = request.signedEvent!!
|
|
||||||
|
|
||||||
if (rememberType != RememberType.NEVER && isChecked) {
|
|
||||||
val signRelay = if (localEvent.kind == 22242) {
|
|
||||||
if ((groupRelayAuthScopes[groupKey] ?: RelayAuthScope.SPECIFIC) == RelayAuthScope.ALL) {
|
|
||||||
"*"
|
|
||||||
} else {
|
|
||||||
RelayUrlUtils.extractHostAndPort(AmberEvent.relay(localEvent))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
""
|
RelayUrlUtils.extractHostAndPort(AmberEvent.relay(localEvent))
|
||||||
}
|
}
|
||||||
AmberUtils.acceptOrRejectPermission(
|
} else {
|
||||||
application = application,
|
""
|
||||||
key = localKey,
|
|
||||||
signerType = SignerType.SIGN_EVENT,
|
|
||||||
kind = localEvent.kind,
|
|
||||||
value = true,
|
|
||||||
rememberType = rememberType,
|
|
||||||
account = thisAccount,
|
|
||||||
relay = signRelay,
|
|
||||||
encryptedData = request.encryptedData,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
AmberUtils.acceptOrRejectPermission(
|
||||||
|
application = application,
|
||||||
|
key = localKey,
|
||||||
|
signerType = SignerType.SIGN_EVENT,
|
||||||
|
kind = localEvent.kind,
|
||||||
|
value = isApproved,
|
||||||
|
rememberType = rememberType,
|
||||||
|
account = thisAccount,
|
||||||
|
relay = signRelay,
|
||||||
|
encryptedData = request.encryptedData,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
dao.insertApplicationWithPermissions(application)
|
||||||
|
|
||||||
|
historyDatabase.dao().addHistory(
|
||||||
|
listOf(
|
||||||
|
HistoryEntity(
|
||||||
|
id = 0,
|
||||||
|
pkKey = localKey,
|
||||||
|
type = SignerType.SIGN_EVENT.toString(),
|
||||||
|
kind = localEvent.kind,
|
||||||
|
time = TimeUtils.now(),
|
||||||
|
accepted = isApproved,
|
||||||
|
content = localEvent.toJson(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
thisAccount.npub,
|
||||||
|
)
|
||||||
|
|
||||||
|
BunkerRequestUtils.remove(request.request.id)
|
||||||
|
|
||||||
|
if (isApproved) {
|
||||||
|
BunkerRequestUtils.sendBunkerResponse(
|
||||||
|
context,
|
||||||
|
thisAccount,
|
||||||
|
request,
|
||||||
|
BunkerResponse(request.request.id, localEvent.toJson(), null),
|
||||||
|
application.application.relays,
|
||||||
|
onLoading = {},
|
||||||
|
onDone = {},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AmberUtils.sendBunkerError(
|
||||||
|
account = thisAccount,
|
||||||
|
bunkerRequest = request,
|
||||||
|
relays = application.application.relays,
|
||||||
|
context = context,
|
||||||
|
closeApplication = application.application.closeApplication,
|
||||||
|
onLoading = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (request.request is BunkerRequestConnect) {
|
||||||
|
if (savedApplication == null) {
|
||||||
dao.insertApplicationWithPermissions(application)
|
dao.insertApplicationWithPermissions(application)
|
||||||
|
|
||||||
historyDatabase.dao().addHistory(
|
historyDatabase.dao().addHistory(
|
||||||
listOf(
|
listOf(
|
||||||
HistoryEntity(
|
HistoryEntity(
|
||||||
id = 0,
|
0,
|
||||||
pkKey = localKey,
|
localKey,
|
||||||
type = SignerType.SIGN_EVENT.toString(),
|
SignerType.CONNECT.toString(),
|
||||||
kind = localEvent.kind,
|
null,
|
||||||
time = TimeUtils.now(),
|
TimeUtils.now(),
|
||||||
accepted = isChecked,
|
isApproved,
|
||||||
content = localEvent.toJson(),
|
content = "",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
thisAccount.npub,
|
thisAccount.npub,
|
||||||
)
|
)
|
||||||
|
|
||||||
BunkerRequestUtils.remove(request.request.id)
|
BunkerRequestUtils.remove(request.request.id)
|
||||||
|
if (isApproved) {
|
||||||
if (isChecked) {
|
|
||||||
BunkerRequestUtils.sendBunkerResponse(
|
BunkerRequestUtils.sendBunkerResponse(
|
||||||
context,
|
context,
|
||||||
thisAccount,
|
thisAccount,
|
||||||
request,
|
request,
|
||||||
BunkerResponse(request.request.id, localEvent.toJson(), null),
|
BunkerResponse(request.request.id, "", null),
|
||||||
application.application.relays,
|
application.application.relays,
|
||||||
onLoading = {},
|
onLoading = {},
|
||||||
onDone = {},
|
onDone = {},
|
||||||
@@ -456,93 +354,54 @@ fun BunkerMultiEventHomeScreen(
|
|||||||
onLoading = {},
|
onLoading = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (request.request is BunkerRequestConnect) {
|
}
|
||||||
if (savedApplication == null) {
|
} else {
|
||||||
dao.insertApplicationWithPermissions(application)
|
val type = requestType
|
||||||
|
if (rememberType != RememberType.NEVER) {
|
||||||
historyDatabase.dao().addHistory(
|
val decryptTypeScope = groupDecryptScopes[groupKey] ?: defaultDecryptTypeScope(type)
|
||||||
listOf(
|
val permissionKind = if (type == SignerType.NIP44_V3_ENCRYPT || type == SignerType.NIP44_V3_DECRYPT) {
|
||||||
HistoryEntity(
|
if (decryptTypeScope == DecryptTypeScope.SPECIFIC) BunkerRequestUtils.getNip44v3Kind(request.request) else null
|
||||||
0,
|
} else {
|
||||||
localKey,
|
null
|
||||||
SignerType.CONNECT.toString(),
|
|
||||||
null,
|
|
||||||
TimeUtils.now(),
|
|
||||||
isChecked,
|
|
||||||
content = "",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
thisAccount.npub,
|
|
||||||
)
|
|
||||||
|
|
||||||
BunkerRequestUtils.remove(request.request.id)
|
|
||||||
if (isChecked) {
|
|
||||||
BunkerRequestUtils.sendBunkerResponse(
|
|
||||||
context,
|
|
||||||
thisAccount,
|
|
||||||
request,
|
|
||||||
BunkerResponse(request.request.id, "", null),
|
|
||||||
application.application.relays,
|
|
||||||
onLoading = {},
|
|
||||||
onDone = {},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
AmberUtils.sendBunkerError(
|
|
||||||
account = thisAccount,
|
|
||||||
bunkerRequest = request,
|
|
||||||
relays = application.application.relays,
|
|
||||||
context = context,
|
|
||||||
closeApplication = application.application.closeApplication,
|
|
||||||
onLoading = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
AmberUtils.acceptOrRejectPermission(
|
||||||
val type = requestType
|
application,
|
||||||
if (rememberType != RememberType.NEVER && isChecked) {
|
localKey,
|
||||||
val decryptTypeScope = groupDecryptScopes[groupKey] ?: defaultDecryptTypeScope(type)
|
type,
|
||||||
val permissionKind = if (type == SignerType.NIP44_V3_ENCRYPT || type == SignerType.NIP44_V3_DECRYPT) {
|
permissionKind,
|
||||||
if (decryptTypeScope == DecryptTypeScope.SPECIFIC) BunkerRequestUtils.getNip44v3Kind(request.request) else null
|
isApproved,
|
||||||
} else {
|
rememberType,
|
||||||
null
|
thisAccount,
|
||||||
}
|
encryptedData = request.encryptedData,
|
||||||
AmberUtils.acceptOrRejectPermission(
|
decryptTypeScope = decryptTypeScope,
|
||||||
application,
|
|
||||||
localKey,
|
|
||||||
type,
|
|
||||||
permissionKind,
|
|
||||||
true,
|
|
||||||
rememberType,
|
|
||||||
thisAccount,
|
|
||||||
encryptedData = request.encryptedData,
|
|
||||||
decryptTypeScope = decryptTypeScope,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
dao.insertApplicationWithPermissions(application)
|
|
||||||
|
|
||||||
historyDatabase.dao().addHistory(
|
|
||||||
listOf(
|
|
||||||
HistoryEntity(
|
|
||||||
0,
|
|
||||||
localKey,
|
|
||||||
type.toString(),
|
|
||||||
null,
|
|
||||||
TimeUtils.now(),
|
|
||||||
isChecked,
|
|
||||||
content = if (type == SignerType.NIP04_DECRYPT || type == SignerType.NIP44_DECRYPT || type == SignerType.DECRYPT_ZAP_EVENT) {
|
|
||||||
request.encryptedData?.result ?: ""
|
|
||||||
} else {
|
|
||||||
request.request.params.getOrElse(1) { "" }
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
thisAccount.npub,
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val signature = request.encryptedData?.result ?: continue
|
dao.insertApplicationWithPermissions(application)
|
||||||
BunkerRequestUtils.remove(request.request.id)
|
|
||||||
if (isChecked) {
|
historyDatabase.dao().addHistory(
|
||||||
|
listOf(
|
||||||
|
HistoryEntity(
|
||||||
|
0,
|
||||||
|
localKey,
|
||||||
|
type.toString(),
|
||||||
|
null,
|
||||||
|
TimeUtils.now(),
|
||||||
|
isApproved,
|
||||||
|
content = if (type == SignerType.NIP04_DECRYPT || type == SignerType.NIP44_DECRYPT || type == SignerType.DECRYPT_ZAP_EVENT) {
|
||||||
|
request.encryptedData?.result ?: ""
|
||||||
|
} else {
|
||||||
|
request.request.params.getOrElse(1) { "" }
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
thisAccount.npub,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isApproved) {
|
||||||
|
val signature = request.encryptedData?.result
|
||||||
|
if (signature != null) {
|
||||||
|
BunkerRequestUtils.remove(request.request.id)
|
||||||
BunkerRequestUtils.sendBunkerResponse(
|
BunkerRequestUtils.sendBunkerResponse(
|
||||||
context,
|
context,
|
||||||
thisAccount,
|
thisAccount,
|
||||||
@@ -552,25 +411,26 @@ fun BunkerMultiEventHomeScreen(
|
|||||||
onLoading = {},
|
onLoading = {},
|
||||||
onDone = {},
|
onDone = {},
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
AmberUtils.sendBunkerError(
|
|
||||||
account = thisAccount,
|
|
||||||
bunkerRequest = request,
|
|
||||||
relays = application.application.relays,
|
|
||||||
context = context,
|
|
||||||
closeApplication = application.application.closeApplication,
|
|
||||||
onLoading = {},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
BunkerRequestUtils.remove(request.request.id)
|
||||||
|
AmberUtils.sendBunkerError(
|
||||||
|
account = thisAccount,
|
||||||
|
bunkerRequest = request,
|
||||||
|
relays = application.application.relays,
|
||||||
|
context = context,
|
||||||
|
closeApplication = application.application.closeApplication,
|
||||||
|
onLoading = {},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
onLoading(false)
|
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
onLoading(false)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
},
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,8 +438,8 @@ fun BunkerMultiEventHomeScreen(
|
|||||||
private fun BunkerRequestCard(
|
private fun BunkerRequestCard(
|
||||||
context: Context,
|
context: Context,
|
||||||
bunkerRequest: AmberBunkerRequest,
|
bunkerRequest: AmberBunkerRequest,
|
||||||
checked: Boolean,
|
approved: Boolean,
|
||||||
onToggleChecked: () -> Unit,
|
onApproveChanged: (Boolean) -> 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) }
|
||||||
@@ -659,42 +519,49 @@ private fun BunkerRequestCard(
|
|||||||
),
|
),
|
||||||
border = BorderStroke(1.dp, Color.Gray),
|
border = BorderStroke(1.dp, Color.Gray),
|
||||||
) {
|
) {
|
||||||
Row(
|
Column(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
Modifier.padding(8.dp),
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable { onToggleChecked() },
|
|
||||||
) {
|
) {
|
||||||
Checkbox(
|
Row(
|
||||||
checked = checked,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
onCheckedChange = { onToggleChecked() },
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CheckboxDefaults.colors().copy(
|
|
||||||
uncheckedBorderColor = Color.Gray,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
Column(
|
|
||||||
Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(top = 8.dp, bottom = 8.dp, end = 8.dp),
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(
|
||||||
text = label,
|
Modifier
|
||||||
color = if (checked) Color.Unspecified else Color.Gray,
|
.weight(1f)
|
||||||
)
|
.padding(end = 8.dp),
|
||||||
if (preview.isNotBlank()) {
|
) {
|
||||||
Text(
|
Text(text = label)
|
||||||
text = preview,
|
if (preview.isNotBlank()) {
|
||||||
color = Color.Gray,
|
Text(
|
||||||
maxLines = 2,
|
text = preview,
|
||||||
)
|
color = Color.Gray,
|
||||||
}
|
maxLines = 2,
|
||||||
if (hasDetails) {
|
)
|
||||||
RawJsonButton(
|
}
|
||||||
onCLick = { showDetails = true },
|
if (hasDetails) {
|
||||||
text = stringResource(R.string.show_details),
|
RawJsonButton(
|
||||||
)
|
onCLick = { showDetails = true },
|
||||||
|
text = stringResource(R.string.show_details),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AmberToggles(
|
||||||
|
selected = approved,
|
||||||
|
options = listOf(true, false),
|
||||||
|
onSelected = onApproveChanged,
|
||||||
|
label = {
|
||||||
|
if (it) stringResource(R.string.approve) else stringResource(R.string.deny)
|
||||||
|
},
|
||||||
|
indicatorColor = {
|
||||||
|
if (it) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
|
||||||
|
},
|
||||||
|
selectedTextColor = {
|
||||||
|
if (it) Color.Black else MaterialTheme.colorScheme.onError
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+198
-323
@@ -4,22 +4,16 @@ import android.app.Activity
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
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.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
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
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.CheckboxDefaults
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TriStateCheckbox
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -32,7 +26,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.state.ToggleableState
|
|
||||||
import androidx.compose.ui.text.capitalize
|
import androidx.compose.ui.text.capitalize
|
||||||
import androidx.compose.ui.text.intl.Locale
|
import androidx.compose.ui.text.intl.Locale
|
||||||
import androidx.compose.ui.text.toLowerCase
|
import androidx.compose.ui.text.toLowerCase
|
||||||
@@ -58,7 +51,6 @@ import com.greenart7c3.nostrsigner.service.MultiEventScreenIntents
|
|||||||
import com.greenart7c3.nostrsigner.service.RelayUrlUtils
|
import com.greenart7c3.nostrsigner.service.RelayUrlUtils
|
||||||
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
||||||
import com.greenart7c3.nostrsigner.ui.RememberType
|
import com.greenart7c3.nostrsigner.ui.RememberType
|
||||||
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 kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
@@ -82,6 +74,7 @@ fun IntentMultiEventHomeScreen(
|
|||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
MultiEventScreenIntents.checkedStates.clear()
|
MultiEventScreenIntents.checkedStates.clear()
|
||||||
MultiEventScreenIntents.rememberType = RememberType.NEVER
|
MultiEventScreenIntents.rememberType = RememberType.NEVER
|
||||||
|
// checkedStates now holds the per-request decision: true = Approve, false = Deny.
|
||||||
intents.forEach { MultiEventScreenIntents.checkedStates[it.id] = true }
|
intents.forEach { MultiEventScreenIntents.checkedStates[it.id] = true }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,30 +92,6 @@ fun IntentMultiEventHomeScreen(
|
|||||||
|
|
||||||
SigningAs(accountParam)
|
SigningAs(accountParam)
|
||||||
|
|
||||||
val allCheckedState = when {
|
|
||||||
intents.all { MultiEventScreenIntents.checkedStates[it.id] ?: true } -> ToggleableState.On
|
|
||||||
intents.none { MultiEventScreenIntents.checkedStates[it.id] ?: true } -> ToggleableState.Off
|
|
||||||
else -> ToggleableState.Indeterminate
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable {
|
|
||||||
val newValue = allCheckedState != ToggleableState.On
|
|
||||||
MultiEventScreenIntents.checkedStates.putAll(intents.associate { it.id to newValue })
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
TriStateCheckbox(
|
|
||||||
state = allCheckedState,
|
|
||||||
onClick = {
|
|
||||||
val newValue = allCheckedState != ToggleableState.On
|
|
||||||
MultiEventScreenIntents.checkedStates.putAll(intents.associate { it.id to newValue })
|
|
||||||
},
|
|
||||||
)
|
|
||||||
Text(stringResource(R.string.select_deselect_all))
|
|
||||||
}
|
|
||||||
|
|
||||||
val groups = remember(intents) {
|
val groups = remember(intents) {
|
||||||
groupRequests(intents) {
|
groupRequests(intents) {
|
||||||
requestGroupKey(it.type, it.event?.kind, it.encryptedData, it.nip44v3Kind)
|
requestGroupKey(it.type, it.event?.kind, it.encryptedData, it.nip44v3Kind)
|
||||||
@@ -136,19 +105,14 @@ fun IntentMultiEventHomeScreen(
|
|||||||
val expanded = groups.size == 1 || (expandedGroups[groupKey] ?: false)
|
val expanded = groups.size == 1 || (expandedGroups[groupKey] ?: false)
|
||||||
if (groups.size > 1) {
|
if (groups.size > 1) {
|
||||||
item(key = "group-header:${groupKey.type.name}:${groupKey.payload?.name ?: ""}:${groupKey.kind ?: ""}") {
|
item(key = "group-header:${groupKey.type.name}:${groupKey.payload?.name ?: ""}:${groupKey.kind ?: ""}") {
|
||||||
val groupState = when {
|
val groupApproved = groupIntents.all { MultiEventScreenIntents.checkedStates[it.id] ?: true }
|
||||||
groupIntents.all { MultiEventScreenIntents.checkedStates[it.id] ?: true } -> ToggleableState.On
|
|
||||||
groupIntents.none { MultiEventScreenIntents.checkedStates[it.id] ?: true } -> ToggleableState.Off
|
|
||||||
else -> ToggleableState.Indeterminate
|
|
||||||
}
|
|
||||||
RequestGroupHeader(
|
RequestGroupHeader(
|
||||||
label = groupKey.toLabel(context),
|
label = groupKey.toLabel(context),
|
||||||
count = groupIntents.size,
|
count = groupIntents.size,
|
||||||
state = groupState,
|
approved = groupApproved,
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
onToggle = {
|
onApproveChanged = { approve ->
|
||||||
val newValue = groupState != ToggleableState.On
|
MultiEventScreenIntents.checkedStates.putAll(groupIntents.associate { it.id to approve })
|
||||||
MultiEventScreenIntents.checkedStates.putAll(groupIntents.associate { it.id to newValue })
|
|
||||||
},
|
},
|
||||||
onExpandToggle = {
|
onExpandToggle = {
|
||||||
expandedGroups[groupKey] = !(expandedGroups[groupKey] ?: false)
|
expandedGroups[groupKey] = !(expandedGroups[groupKey] ?: false)
|
||||||
@@ -174,10 +138,9 @@ fun IntentMultiEventHomeScreen(
|
|||||||
IntentRequestCard(
|
IntentRequestCard(
|
||||||
context = context,
|
context = context,
|
||||||
intent = intent,
|
intent = intent,
|
||||||
checked = MultiEventScreenIntents.checkedStates[intent.id] ?: true,
|
approved = MultiEventScreenIntents.checkedStates[intent.id] ?: true,
|
||||||
onToggleChecked = {
|
onApproveChanged = {
|
||||||
val current = MultiEventScreenIntents.checkedStates[intent.id] ?: true
|
MultiEventScreenIntents.checkedStates[intent.id] = it
|
||||||
MultiEventScreenIntents.checkedStates[intent.id] = !current
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -185,19 +148,16 @@ fun IntentMultiEventHomeScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
AmberButton(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 16.dp),
|
.padding(vertical = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
text = stringResource(R.string.confirm),
|
||||||
) {
|
onClick = {
|
||||||
AmberButton(
|
onLoading(true)
|
||||||
Modifier.weight(1f),
|
Amber.instance.applicationIOScope.launch(Dispatchers.IO) {
|
||||||
colors = ButtonDefaults.buttonColors().copy(
|
try {
|
||||||
containerColor = orange,
|
val results = mutableListOf<Result>()
|
||||||
),
|
|
||||||
onClick = {
|
|
||||||
Amber.instance.applicationIOScope.launch(Dispatchers.IO) {
|
|
||||||
var closeApp = true
|
var closeApp = true
|
||||||
onRemoveIntentData(intents, IntentResultType.REMOVE)
|
onRemoveIntentData(intents, IntentResultType.REMOVE)
|
||||||
val localKey = packageName ?: return@launch
|
val localKey = packageName ?: return@launch
|
||||||
@@ -211,6 +171,7 @@ fun IntentMultiEventHomeScreen(
|
|||||||
} ?: continue
|
} ?: continue
|
||||||
|
|
||||||
val dao = Amber.instance.dao(thisAccount.npub)
|
val dao = Amber.instance.dao(thisAccount.npub)
|
||||||
|
val historyDatabase = Amber.instance.getHistoryDatabase(thisAccount.npub)
|
||||||
val application = dao.getByKey(localKey) ?: ApplicationWithPermissions(
|
val application = dao.getByKey(localKey) ?: ApplicationWithPermissions(
|
||||||
application = ApplicationEntity(
|
application = ApplicationEntity(
|
||||||
localKey,
|
localKey,
|
||||||
@@ -236,247 +197,163 @@ fun IntentMultiEventHomeScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var permissionsChanged = false
|
var permissionsChanged = false
|
||||||
|
val historyList = mutableListOf<HistoryEntity>()
|
||||||
|
|
||||||
for (intentData in accountIntents) {
|
for (intentData in accountIntents) {
|
||||||
val isChecked = MultiEventScreenIntents.checkedStates[intentData.id] ?: true
|
val isApproved = MultiEventScreenIntents.checkedStates[intentData.id] ?: true
|
||||||
val groupKey = requestGroupKey(intentData.type, intentData.event?.kind, intentData.encryptedData, intentData.nip44v3Kind)
|
val type = intentData.type
|
||||||
|
val groupKey = requestGroupKey(type, intentData.event?.kind, intentData.encryptedData, intentData.nip44v3Kind)
|
||||||
val rememberType = groupRememberTypes[groupKey] ?: RememberType.NEVER
|
val rememberType = groupRememberTypes[groupKey] ?: RememberType.NEVER
|
||||||
if (rememberType != RememberType.NEVER && isChecked) {
|
|
||||||
val decryptTypeScope = groupDecryptScopes[groupKey] ?: defaultDecryptTypeScope(intentData.type)
|
if (type == SignerType.SIGN_EVENT) {
|
||||||
val rejectKind = when {
|
val localEvent = intentData.event!!
|
||||||
intentData.type == SignerType.SIGN_EVENT -> intentData.event?.kind
|
|
||||||
intentData.type == SignerType.NIP44_V3_ENCRYPT || intentData.type == SignerType.NIP44_V3_DECRYPT ->
|
if (rememberType != RememberType.NEVER) {
|
||||||
if (decryptTypeScope == DecryptTypeScope.SPECIFIC) intentData.nip44v3Kind else null
|
val signRelay = if (localEvent.kind == 22242) {
|
||||||
else -> null
|
if ((groupRelayAuthScopes[groupKey] ?: RelayAuthScope.SPECIFIC) == RelayAuthScope.ALL) {
|
||||||
}
|
"*"
|
||||||
val rejectRelay = if (intentData.type == SignerType.SIGN_EVENT && intentData.event?.kind == 22242) {
|
} else {
|
||||||
if ((groupRelayAuthScopes[groupKey] ?: RelayAuthScope.SPECIFIC) == RelayAuthScope.ALL) {
|
RelayUrlUtils.extractHostAndPort(AmberEvent.relay(localEvent))
|
||||||
"*"
|
}
|
||||||
} else {
|
} else {
|
||||||
RelayUrlUtils.extractHostAndPort(AmberEvent.relay(intentData.event))
|
""
|
||||||
|
}
|
||||||
|
AmberUtils.updatePermission(
|
||||||
|
application,
|
||||||
|
localKey,
|
||||||
|
type,
|
||||||
|
localEvent.kind,
|
||||||
|
isApproved,
|
||||||
|
rememberType,
|
||||||
|
relay = signRelay,
|
||||||
|
encryptedData = intentData.encryptedData,
|
||||||
|
)
|
||||||
|
permissionsChanged = true
|
||||||
|
}
|
||||||
|
|
||||||
|
historyList.add(
|
||||||
|
HistoryEntity(
|
||||||
|
0,
|
||||||
|
localKey,
|
||||||
|
type.toString(),
|
||||||
|
localEvent.kind,
|
||||||
|
TimeUtils.now(),
|
||||||
|
isApproved,
|
||||||
|
content = localEvent.toJson(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isApproved) {
|
||||||
|
val signature = if (localEvent is LnZapRequestEvent &&
|
||||||
|
localEvent.tags.any { tag ->
|
||||||
|
tag.any { t -> t == "anon" }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
localEvent.toJson()
|
||||||
|
} else {
|
||||||
|
localEvent.sig
|
||||||
|
}
|
||||||
|
results.add(
|
||||||
|
Result(
|
||||||
|
null,
|
||||||
|
signature = signature,
|
||||||
|
result = signature,
|
||||||
|
id = intentData.id,
|
||||||
|
rejected = null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
results.add(
|
||||||
|
Result(
|
||||||
|
null,
|
||||||
|
signature = null,
|
||||||
|
result = null,
|
||||||
|
id = intentData.id,
|
||||||
|
rejected = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (rememberType != RememberType.NEVER) {
|
||||||
|
val decryptTypeScope = groupDecryptScopes[groupKey] ?: defaultDecryptTypeScope(type)
|
||||||
|
val permissionKind = if (type == SignerType.NIP44_V3_ENCRYPT || type == SignerType.NIP44_V3_DECRYPT) {
|
||||||
|
if (decryptTypeScope == DecryptTypeScope.SPECIFIC) intentData.nip44v3Kind else null
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
AmberUtils.updatePermission(
|
||||||
|
application,
|
||||||
|
localKey,
|
||||||
|
type,
|
||||||
|
permissionKind,
|
||||||
|
isApproved,
|
||||||
|
rememberType,
|
||||||
|
encryptedData = intentData.encryptedData,
|
||||||
|
decryptTypeScope = decryptTypeScope,
|
||||||
|
)
|
||||||
|
permissionsChanged = true
|
||||||
|
}
|
||||||
|
|
||||||
|
historyList.add(
|
||||||
|
HistoryEntity(
|
||||||
|
0,
|
||||||
|
localKey,
|
||||||
|
type.toString(),
|
||||||
|
null,
|
||||||
|
TimeUtils.now(),
|
||||||
|
isApproved,
|
||||||
|
content = if (type == SignerType.NIP04_DECRYPT || type == SignerType.NIP44_DECRYPT || type == SignerType.DECRYPT_ZAP_EVENT) {
|
||||||
|
intentData.encryptedData?.result ?: ""
|
||||||
|
} else {
|
||||||
|
intentData.data
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isApproved) {
|
||||||
|
val signature = intentData.encryptedData?.result
|
||||||
|
if (signature != null) {
|
||||||
|
results.add(
|
||||||
|
Result(
|
||||||
|
null,
|
||||||
|
signature = signature,
|
||||||
|
result = signature,
|
||||||
|
id = intentData.id,
|
||||||
|
rejected = null,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
""
|
results.add(
|
||||||
|
Result(
|
||||||
|
null,
|
||||||
|
signature = null,
|
||||||
|
result = null,
|
||||||
|
id = intentData.id,
|
||||||
|
rejected = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
AmberUtils.updatePermission(
|
|
||||||
application,
|
|
||||||
localKey,
|
|
||||||
intentData.type,
|
|
||||||
rejectKind,
|
|
||||||
false,
|
|
||||||
rememberType,
|
|
||||||
relay = rejectRelay,
|
|
||||||
encryptedData = intentData.encryptedData,
|
|
||||||
decryptTypeScope = decryptTypeScope,
|
|
||||||
)
|
|
||||||
permissionsChanged = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (permissionsChanged || application.application.key.isBlank()) {
|
if (permissionsChanged || application.application.key.isBlank()) {
|
||||||
dao.insertApplicationWithPermissions(application)
|
dao.insertApplicationWithPermissions(application)
|
||||||
}
|
}
|
||||||
|
historyDatabase.dao().addHistory(historyList, thisAccount.npub)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.isNotEmpty()) {
|
||||||
|
sendResultIntent(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendRejectIntent(
|
|
||||||
results = intents.map {
|
|
||||||
Result(
|
|
||||||
null,
|
|
||||||
signature = null,
|
|
||||||
result = null,
|
|
||||||
id = it.id,
|
|
||||||
rejected = true,
|
|
||||||
)
|
|
||||||
}.toMutableList(),
|
|
||||||
)
|
|
||||||
finishActivity(closeApp)
|
finishActivity(closeApp)
|
||||||
|
} finally {
|
||||||
|
onLoading(false)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
text = stringResource(R.string.discard_all),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
AmberButton(
|
|
||||||
Modifier.weight(1f),
|
|
||||||
text = stringResource(R.string.approve_all),
|
|
||||||
onClick = {
|
|
||||||
onLoading(true)
|
|
||||||
Amber.instance.applicationIOScope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
val results = mutableListOf<Result>()
|
|
||||||
var closeApp = true
|
|
||||||
onRemoveIntentData(intents, IntentResultType.REMOVE)
|
|
||||||
val localKey = packageName ?: return@launch
|
|
||||||
val intentsByAccount = intents.groupBy { it.currentAccount.ifBlank { accountParam.npub } }
|
|
||||||
|
|
||||||
for ((accountNpub, accountIntents) in intentsByAccount) {
|
|
||||||
val thisAccount = if (accountNpub == accountParam.npub) {
|
|
||||||
accountParam
|
|
||||||
} else {
|
|
||||||
LocalPreferences.loadFromEncryptedStorage(context, accountNpub)
|
|
||||||
} ?: continue
|
|
||||||
|
|
||||||
val dao = Amber.instance.dao(thisAccount.npub)
|
|
||||||
val historyDatabase = Amber.instance.getHistoryDatabase(thisAccount.npub)
|
|
||||||
val application = dao.getByKey(localKey) ?: ApplicationWithPermissions(
|
|
||||||
application = ApplicationEntity(
|
|
||||||
localKey,
|
|
||||||
"",
|
|
||||||
listOf(),
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
thisAccount.hexKey,
|
|
||||||
true,
|
|
||||||
"",
|
|
||||||
false,
|
|
||||||
thisAccount.signPolicy,
|
|
||||||
true,
|
|
||||||
0L,
|
|
||||||
lastUsed = TimeUtils.now(),
|
|
||||||
),
|
|
||||||
permissions = mutableListOf(),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!application.application.closeApplication) {
|
|
||||||
closeApp = false
|
|
||||||
}
|
|
||||||
|
|
||||||
var permissionsChanged = false
|
|
||||||
val historyList = mutableListOf<HistoryEntity>()
|
|
||||||
|
|
||||||
for (intentData in accountIntents) {
|
|
||||||
val isChecked = MultiEventScreenIntents.checkedStates[intentData.id] ?: true
|
|
||||||
val type = intentData.type
|
|
||||||
val groupKey = requestGroupKey(type, intentData.event?.kind, intentData.encryptedData, intentData.nip44v3Kind)
|
|
||||||
val rememberType = groupRememberTypes[groupKey] ?: RememberType.NEVER
|
|
||||||
|
|
||||||
if (type == SignerType.SIGN_EVENT) {
|
|
||||||
val localEvent = intentData.event!!
|
|
||||||
|
|
||||||
if (rememberType != RememberType.NEVER && isChecked) {
|
|
||||||
val signRelay = if (localEvent.kind == 22242) {
|
|
||||||
if ((groupRelayAuthScopes[groupKey] ?: RelayAuthScope.SPECIFIC) == RelayAuthScope.ALL) {
|
|
||||||
"*"
|
|
||||||
} else {
|
|
||||||
RelayUrlUtils.extractHostAndPort(AmberEvent.relay(localEvent))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
AmberUtils.updatePermission(
|
|
||||||
application,
|
|
||||||
localKey,
|
|
||||||
type,
|
|
||||||
localEvent.kind,
|
|
||||||
true,
|
|
||||||
rememberType,
|
|
||||||
relay = signRelay,
|
|
||||||
encryptedData = intentData.encryptedData,
|
|
||||||
)
|
|
||||||
permissionsChanged = true
|
|
||||||
}
|
|
||||||
|
|
||||||
historyList.add(
|
|
||||||
HistoryEntity(
|
|
||||||
0,
|
|
||||||
localKey,
|
|
||||||
type.toString(),
|
|
||||||
localEvent.kind,
|
|
||||||
TimeUtils.now(),
|
|
||||||
isChecked,
|
|
||||||
content = localEvent.toJson(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isChecked) {
|
|
||||||
val signature = if (localEvent is LnZapRequestEvent &&
|
|
||||||
localEvent.tags.any { tag ->
|
|
||||||
tag.any { t -> t == "anon" }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
localEvent.toJson()
|
|
||||||
} else {
|
|
||||||
localEvent.sig
|
|
||||||
}
|
|
||||||
results.add(
|
|
||||||
Result(
|
|
||||||
null,
|
|
||||||
signature = signature,
|
|
||||||
result = signature,
|
|
||||||
id = intentData.id,
|
|
||||||
rejected = null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (rememberType != RememberType.NEVER && isChecked) {
|
|
||||||
val decryptTypeScope = groupDecryptScopes[groupKey] ?: defaultDecryptTypeScope(type)
|
|
||||||
val permissionKind = if (type == SignerType.NIP44_V3_ENCRYPT || type == SignerType.NIP44_V3_DECRYPT) {
|
|
||||||
if (decryptTypeScope == DecryptTypeScope.SPECIFIC) intentData.nip44v3Kind else null
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
AmberUtils.updatePermission(
|
|
||||||
application,
|
|
||||||
localKey,
|
|
||||||
type,
|
|
||||||
permissionKind,
|
|
||||||
true,
|
|
||||||
rememberType,
|
|
||||||
encryptedData = intentData.encryptedData,
|
|
||||||
decryptTypeScope = decryptTypeScope,
|
|
||||||
)
|
|
||||||
permissionsChanged = true
|
|
||||||
}
|
|
||||||
|
|
||||||
historyList.add(
|
|
||||||
HistoryEntity(
|
|
||||||
0,
|
|
||||||
localKey,
|
|
||||||
type.toString(),
|
|
||||||
null,
|
|
||||||
TimeUtils.now(),
|
|
||||||
isChecked,
|
|
||||||
content = if (type == SignerType.NIP04_DECRYPT || type == SignerType.NIP44_DECRYPT || type == SignerType.DECRYPT_ZAP_EVENT) {
|
|
||||||
intentData.encryptedData?.result ?: ""
|
|
||||||
} else {
|
|
||||||
intentData.data
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
val signature = intentData.encryptedData?.result
|
|
||||||
if (isChecked && signature != null) {
|
|
||||||
results.add(
|
|
||||||
Result(
|
|
||||||
null,
|
|
||||||
signature = signature,
|
|
||||||
result = signature,
|
|
||||||
id = intentData.id,
|
|
||||||
rejected = null,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (permissionsChanged || application.application.key.isBlank()) {
|
|
||||||
dao.insertApplicationWithPermissions(application)
|
|
||||||
}
|
|
||||||
historyDatabase.dao().addHistory(historyList, thisAccount.npub)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (results.isNotEmpty()) {
|
|
||||||
sendResultIntent(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
finishActivity(closeApp)
|
|
||||||
} finally {
|
|
||||||
onLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,8 +361,8 @@ fun IntentMultiEventHomeScreen(
|
|||||||
private fun IntentRequestCard(
|
private fun IntentRequestCard(
|
||||||
context: Context,
|
context: Context,
|
||||||
intent: IntentData,
|
intent: IntentData,
|
||||||
checked: Boolean,
|
approved: Boolean,
|
||||||
onToggleChecked: () -> Unit,
|
onApproveChanged: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
val type = intent.type
|
val type = intent.type
|
||||||
var showDetails by remember { mutableStateOf(false) }
|
var showDetails by remember { mutableStateOf(false) }
|
||||||
@@ -569,42 +446,49 @@ private fun IntentRequestCard(
|
|||||||
),
|
),
|
||||||
border = BorderStroke(1.dp, Color.Gray),
|
border = BorderStroke(1.dp, Color.Gray),
|
||||||
) {
|
) {
|
||||||
Row(
|
Column(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
Modifier.padding(8.dp),
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable { onToggleChecked() },
|
|
||||||
) {
|
) {
|
||||||
Checkbox(
|
Row(
|
||||||
checked = checked,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
onCheckedChange = { onToggleChecked() },
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = CheckboxDefaults.colors().copy(
|
|
||||||
uncheckedBorderColor = Color.Gray,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
Column(
|
|
||||||
Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(top = 8.dp, bottom = 8.dp, end = 8.dp),
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(
|
||||||
text = label,
|
Modifier
|
||||||
color = if (checked) Color.Unspecified else Color.Gray,
|
.weight(1f)
|
||||||
)
|
.padding(end = 8.dp),
|
||||||
if (preview.isNotBlank()) {
|
) {
|
||||||
Text(
|
Text(text = label)
|
||||||
text = preview,
|
if (preview.isNotBlank()) {
|
||||||
color = Color.Gray,
|
Text(
|
||||||
maxLines = 2,
|
text = preview,
|
||||||
)
|
color = Color.Gray,
|
||||||
}
|
maxLines = 2,
|
||||||
if (hasDetails) {
|
)
|
||||||
RawJsonButton(
|
}
|
||||||
onCLick = { showDetails = true },
|
if (hasDetails) {
|
||||||
text = stringResource(R.string.show_details),
|
RawJsonButton(
|
||||||
)
|
onCLick = { showDetails = true },
|
||||||
|
text = stringResource(R.string.show_details),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AmberToggles(
|
||||||
|
selected = approved,
|
||||||
|
options = listOf(true, false),
|
||||||
|
onSelected = onApproveChanged,
|
||||||
|
label = {
|
||||||
|
if (it) stringResource(R.string.approve) else stringResource(R.string.deny)
|
||||||
|
},
|
||||||
|
indicatorColor = {
|
||||||
|
if (it) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
|
||||||
|
},
|
||||||
|
selectedTextColor = {
|
||||||
|
if (it) Color.Black else MaterialTheme.colorScheme.onError
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,15 +516,6 @@ private fun finishActivity(closeApp: Boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendRejectIntent(
|
|
||||||
results: MutableList<Result>,
|
|
||||||
) {
|
|
||||||
val json = Permission.mapper.writeValueAsString(results)
|
|
||||||
val intent = Intent()
|
|
||||||
intent.putExtra("results", json)
|
|
||||||
Amber.instance.getMainActivity()?.setResult(Activity.RESULT_OK, intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sendResultIntent(
|
private fun sendResultIntent(
|
||||||
results: MutableList<Result>,
|
results: MutableList<Result>,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -63,9 +63,10 @@ fun LabeledBorderBox(
|
|||||||
fun RememberMyChoiceToggles(
|
fun RememberMyChoiceToggles(
|
||||||
selected: RememberType,
|
selected: RememberType,
|
||||||
onSelected: (RememberType) -> Unit,
|
onSelected: (RememberType) -> Unit,
|
||||||
|
label: String = stringResource(R.string.automatically_sign_this_for),
|
||||||
) {
|
) {
|
||||||
LabeledBorderBox(
|
LabeledBorderBox(
|
||||||
label = stringResource(R.string.automatically_sign_this_for),
|
label = label,
|
||||||
) {
|
) {
|
||||||
AmberToggles(
|
AmberToggles(
|
||||||
selected = selected,
|
selected = selected,
|
||||||
|
|||||||
@@ -12,14 +12,13 @@ import androidx.compose.material.icons.filled.ExpandMore
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TriStateCheckbox
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.state.ToggleableState
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.greenart7c3.nostrsigner.R
|
import com.greenart7c3.nostrsigner.R
|
||||||
import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind
|
import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind
|
||||||
@@ -226,6 +225,7 @@ fun RequestGroupOptions(
|
|||||||
RememberMyChoiceToggles(
|
RememberMyChoiceToggles(
|
||||||
selected = rememberType,
|
selected = rememberType,
|
||||||
onSelected = onRememberTypeChanged,
|
onSelected = onRememberTypeChanged,
|
||||||
|
label = stringResource(R.string.remember_my_choice_for),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,36 +234,51 @@ fun RequestGroupOptions(
|
|||||||
fun RequestGroupHeader(
|
fun RequestGroupHeader(
|
||||||
label: String,
|
label: String,
|
||||||
count: Int,
|
count: Int,
|
||||||
state: ToggleableState,
|
approved: Boolean,
|
||||||
expanded: Boolean,
|
expanded: Boolean,
|
||||||
onToggle: () -> Unit,
|
onApproveChanged: (Boolean) -> Unit,
|
||||||
onExpandToggle: () -> Unit,
|
onExpandToggle: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val rotation by animateFloatAsState(
|
val rotation by animateFloatAsState(
|
||||||
targetValue = if (expanded) 180f else 0f,
|
targetValue = if (expanded) 180f else 0f,
|
||||||
label = "group header chevron rotation",
|
label = "group header chevron rotation",
|
||||||
)
|
)
|
||||||
Row(
|
Column(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
Modifier.fillMaxWidth(),
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable { onExpandToggle() },
|
|
||||||
) {
|
) {
|
||||||
TriStateCheckbox(
|
Row(
|
||||||
state = state,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
onClick = onToggle,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "$label ($count)",
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
)
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.ExpandMore,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 12.dp)
|
.fillMaxWidth()
|
||||||
.rotate(rotation),
|
.clickable { onExpandToggle() },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "$label ($count)",
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ExpandMore,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 12.dp)
|
||||||
|
.rotate(rotation),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AmberToggles(
|
||||||
|
selected = approved,
|
||||||
|
options = listOf(true, false),
|
||||||
|
onSelected = onApproveChanged,
|
||||||
|
label = {
|
||||||
|
if (it) stringResource(R.string.approve) else stringResource(R.string.deny)
|
||||||
|
},
|
||||||
|
indicatorColor = {
|
||||||
|
if (it) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
|
||||||
|
},
|
||||||
|
selectedTextColor = {
|
||||||
|
if (it) Color.Black else MaterialTheme.colorScheme.onError
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ import com.greenart7c3.nostrsigner.ui.theme.ThemePreviews
|
|||||||
fun ToggleOption(
|
fun ToggleOption(
|
||||||
text: String,
|
text: String,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
|
selectedTextColor: Color,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val textColor by animateColorAsState(
|
val textColor by animateColorAsState(
|
||||||
targetValue = if (isSelected) Color.Black else MaterialTheme.colorScheme.onSurfaceVariant,
|
targetValue = if (isSelected) selectedTextColor else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
animationSpec = tween(durationMillis = 200),
|
animationSpec = tween(durationMillis = 200),
|
||||||
label = "textColor",
|
label = "textColor",
|
||||||
)
|
)
|
||||||
@@ -59,12 +60,14 @@ fun ToggleOptionPreview() {
|
|||||||
ToggleOption(
|
ToggleOption(
|
||||||
text = "Always",
|
text = "Always",
|
||||||
isSelected = true,
|
isSelected = true,
|
||||||
|
selectedTextColor = Color.Black,
|
||||||
modifier = Modifier.width(80.dp),
|
modifier = Modifier.width(80.dp),
|
||||||
onClick = {},
|
onClick = {},
|
||||||
)
|
)
|
||||||
ToggleOption(
|
ToggleOption(
|
||||||
text = "Never",
|
text = "Never",
|
||||||
isSelected = false,
|
isSelected = false,
|
||||||
|
selectedTextColor = Color.Black,
|
||||||
modifier = Modifier.width(80.dp),
|
modifier = Modifier.width(80.dp),
|
||||||
onClick = {},
|
onClick = {},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -630,8 +630,9 @@
|
|||||||
<string name="pubkey">Pubkey</string>
|
<string name="pubkey">Pubkey</string>
|
||||||
<string name="date">Datum</string>
|
<string name="date">Datum</string>
|
||||||
<string name="tags">Tags</string>
|
<string name="tags">Tags</string>
|
||||||
<string name="discard_all">Auswahl verwerfen</string>
|
<string name="approve">Genehmigen</string>
|
||||||
<string name="approve_all">Auswahl genehmigen</string>
|
<string name="confirm">Bestätigen</string>
|
||||||
|
<string name="remember_my_choice_for">Meine Wahl speichern für</string>
|
||||||
<string name="requests_message">Anfragen %1$s</string>
|
<string name="requests_message">Anfragen %1$s</string>
|
||||||
<string name="trust_excellent">Ausgezeichnet</string>
|
<string name="trust_excellent">Ausgezeichnet</string>
|
||||||
<string name="trust_good">Gut</string>
|
<string name="trust_good">Gut</string>
|
||||||
|
|||||||
@@ -633,8 +633,9 @@
|
|||||||
<string name="pubkey">Clave pública</string>
|
<string name="pubkey">Clave pública</string>
|
||||||
<string name="date">Fecha</string>
|
<string name="date">Fecha</string>
|
||||||
<string name="tags">Etiquetas</string>
|
<string name="tags">Etiquetas</string>
|
||||||
<string name="discard_all">Descartar seleccionados</string>
|
<string name="approve">Aprobar</string>
|
||||||
<string name="approve_all">Aprobar seleccionados</string>
|
<string name="confirm">Confirmar</string>
|
||||||
|
<string name="remember_my_choice_for">Recordar mi elección para</string>
|
||||||
<string name="requests_message">Solicitudes %1$s</string>
|
<string name="requests_message">Solicitudes %1$s</string>
|
||||||
<string name="trust_excellent">Excelente</string>
|
<string name="trust_excellent">Excelente</string>
|
||||||
<string name="trust_good">Bueno</string>
|
<string name="trust_good">Bueno</string>
|
||||||
|
|||||||
@@ -630,8 +630,9 @@
|
|||||||
<string name="pubkey">Pubkey</string>
|
<string name="pubkey">Pubkey</string>
|
||||||
<string name="date">Date</string>
|
<string name="date">Date</string>
|
||||||
<string name="tags">Tags</string>
|
<string name="tags">Tags</string>
|
||||||
<string name="discard_all">Rejeter la sélection</string>
|
<string name="approve">Approuver</string>
|
||||||
<string name="approve_all">Approuver la sélection</string>
|
<string name="confirm">Confirmer</string>
|
||||||
|
<string name="remember_my_choice_for">Mémoriser mon choix pour</string>
|
||||||
<string name="requests_message">Requêtes %1$s</string>
|
<string name="requests_message">Requêtes %1$s</string>
|
||||||
<string name="trust_excellent">Excellent</string>
|
<string name="trust_excellent">Excellent</string>
|
||||||
<string name="trust_good">Bon</string>
|
<string name="trust_good">Bon</string>
|
||||||
|
|||||||
@@ -633,8 +633,9 @@
|
|||||||
<string name="pubkey">Pubkey</string>
|
<string name="pubkey">Pubkey</string>
|
||||||
<string name="date">Tanggal</string>
|
<string name="date">Tanggal</string>
|
||||||
<string name="tags">Tag</string>
|
<string name="tags">Tag</string>
|
||||||
<string name="discard_all">Buang yang dipilih</string>
|
<string name="approve">Setujui</string>
|
||||||
<string name="approve_all">Setujui yang dipilih</string>
|
<string name="confirm">Konfirmasi</string>
|
||||||
|
<string name="remember_my_choice_for">Ingat pilihan saya untuk</string>
|
||||||
<string name="requests_message">Permintaan %1$s</string>
|
<string name="requests_message">Permintaan %1$s</string>
|
||||||
<string name="trust_excellent">Sangat baik</string>
|
<string name="trust_excellent">Sangat baik</string>
|
||||||
<string name="trust_good">Baik</string>
|
<string name="trust_good">Baik</string>
|
||||||
|
|||||||
@@ -633,8 +633,9 @@
|
|||||||
<string name="pubkey">Chiave pubblica</string>
|
<string name="pubkey">Chiave pubblica</string>
|
||||||
<string name="date">Data</string>
|
<string name="date">Data</string>
|
||||||
<string name="tags">Tag</string>
|
<string name="tags">Tag</string>
|
||||||
<string name="discard_all">Scarta selezionati</string>
|
<string name="approve">Approva</string>
|
||||||
<string name="approve_all">Approva selezionati</string>
|
<string name="confirm">Conferma</string>
|
||||||
|
<string name="remember_my_choice_for">Ricorda la mia scelta per</string>
|
||||||
<string name="requests_message">Richieste %1$s</string>
|
<string name="requests_message">Richieste %1$s</string>
|
||||||
<string name="trust_excellent">Eccellente</string>
|
<string name="trust_excellent">Eccellente</string>
|
||||||
<string name="trust_good">Buono</string>
|
<string name="trust_good">Buono</string>
|
||||||
|
|||||||
@@ -609,8 +609,9 @@
|
|||||||
<string name="pubkey">Pubkey</string>
|
<string name="pubkey">Pubkey</string>
|
||||||
<string name="date">日付</string>
|
<string name="date">日付</string>
|
||||||
<string name="tags">タグ</string>
|
<string name="tags">タグ</string>
|
||||||
<string name="discard_all">選択した項目を破棄</string>
|
<string name="approve">承認</string>
|
||||||
<string name="approve_all">選択した項目を承認</string>
|
<string name="confirm">確認</string>
|
||||||
|
<string name="remember_my_choice_for">この選択を記憶する対象</string>
|
||||||
<string name="requests_message">リクエスト %1$s</string>
|
<string name="requests_message">リクエスト %1$s</string>
|
||||||
<string name="trust_excellent">非常に高い</string>
|
<string name="trust_excellent">非常に高い</string>
|
||||||
<string name="trust_good">高い</string>
|
<string name="trust_good">高い</string>
|
||||||
|
|||||||
@@ -633,8 +633,9 @@
|
|||||||
<string name="pubkey">공개 키</string>
|
<string name="pubkey">공개 키</string>
|
||||||
<string name="date">날짜</string>
|
<string name="date">날짜</string>
|
||||||
<string name="tags">태그</string>
|
<string name="tags">태그</string>
|
||||||
<string name="discard_all">선택 항목 버리기</string>
|
<string name="approve">승인</string>
|
||||||
<string name="approve_all">선택 항목 승인</string>
|
<string name="confirm">확인</string>
|
||||||
|
<string name="remember_my_choice_for">내 선택 기억</string>
|
||||||
<string name="requests_message">요청 %1$s</string>
|
<string name="requests_message">요청 %1$s</string>
|
||||||
<string name="trust_excellent">매우 좋음</string>
|
<string name="trust_excellent">매우 좋음</string>
|
||||||
<string name="trust_good">좋음</string>
|
<string name="trust_good">좋음</string>
|
||||||
|
|||||||
@@ -628,8 +628,9 @@
|
|||||||
<string name="pubkey">Chave</string>
|
<string name="pubkey">Chave</string>
|
||||||
<string name="date">Data</string>
|
<string name="date">Data</string>
|
||||||
<string name="tags">Tags</string>
|
<string name="tags">Tags</string>
|
||||||
<string name="discard_all">Descartar selecionado</string>
|
<string name="approve">Aprovar</string>
|
||||||
<string name="approve_all">Aprovar selecionados</string>
|
<string name="confirm">Confirmar</string>
|
||||||
|
<string name="remember_my_choice_for">Lembrar minha escolha para</string>
|
||||||
<string name="requests_message">Solicitações %1$s</string>
|
<string name="requests_message">Solicitações %1$s</string>
|
||||||
<string name="trust_excellent">Excelente</string>
|
<string name="trust_excellent">Excelente</string>
|
||||||
<string name="trust_good">Bom</string>
|
<string name="trust_good">Bom</string>
|
||||||
|
|||||||
@@ -633,8 +633,9 @@
|
|||||||
<string name="pubkey">Публичный ключ</string>
|
<string name="pubkey">Публичный ключ</string>
|
||||||
<string name="date">Дата</string>
|
<string name="date">Дата</string>
|
||||||
<string name="tags">Теги</string>
|
<string name="tags">Теги</string>
|
||||||
<string name="discard_all">Отклонить выбранные</string>
|
<string name="approve">Одобрить</string>
|
||||||
<string name="approve_all">Одобрить выбранные</string>
|
<string name="confirm">Подтвердить</string>
|
||||||
|
<string name="remember_my_choice_for">Запомнить мой выбор для</string>
|
||||||
<string name="requests_message">Запросы %1$s</string>
|
<string name="requests_message">Запросы %1$s</string>
|
||||||
<string name="trust_excellent">Отлично</string>
|
<string name="trust_excellent">Отлично</string>
|
||||||
<string name="trust_good">Хорошо</string>
|
<string name="trust_good">Хорошо</string>
|
||||||
|
|||||||
@@ -609,8 +609,9 @@
|
|||||||
<string name="pubkey">Pubkey</string>
|
<string name="pubkey">Pubkey</string>
|
||||||
<string name="date">วันที่</string>
|
<string name="date">วันที่</string>
|
||||||
<string name="tags">แท็ก</string>
|
<string name="tags">แท็ก</string>
|
||||||
<string name="discard_all">ยกเลิกรายการที่เลือก</string>
|
<string name="approve">อนุมัติ</string>
|
||||||
<string name="approve_all">อนุมัติรายการที่เลือก</string>
|
<string name="confirm">ยืนยัน</string>
|
||||||
|
<string name="remember_my_choice_for">จดจำตัวเลือกของฉันสำหรับ</string>
|
||||||
<string name="requests_message">คำขอ %1$s</string>
|
<string name="requests_message">คำขอ %1$s</string>
|
||||||
<string name="trust_excellent">ยอดเยี่ยม</string>
|
<string name="trust_excellent">ยอดเยี่ยม</string>
|
||||||
<string name="trust_good">ดี</string>
|
<string name="trust_good">ดี</string>
|
||||||
|
|||||||
@@ -629,8 +629,9 @@
|
|||||||
<string name="pubkey">Pubkey (Açık anahtar)</string>
|
<string name="pubkey">Pubkey (Açık anahtar)</string>
|
||||||
<string name="date">Tarih</string>
|
<string name="date">Tarih</string>
|
||||||
<string name="tags">Etiketler</string>
|
<string name="tags">Etiketler</string>
|
||||||
<string name="discard_all">Seçilenleri yoksay</string>
|
<string name="approve">Onayla</string>
|
||||||
<string name="approve_all">Seçilenleri onayla</string>
|
<string name="confirm">Doğrula</string>
|
||||||
|
<string name="remember_my_choice_for">Seçimimi şu için hatırla</string>
|
||||||
<string name="requests_message">İstekler: %1$s</string>
|
<string name="requests_message">İstekler: %1$s</string>
|
||||||
<string name="trust_excellent">Mükemmel</string>
|
<string name="trust_excellent">Mükemmel</string>
|
||||||
<string name="trust_good">İyi</string>
|
<string name="trust_good">İyi</string>
|
||||||
|
|||||||
@@ -609,8 +609,9 @@
|
|||||||
<string name="pubkey">Pubkey</string>
|
<string name="pubkey">Pubkey</string>
|
||||||
<string name="date">Ngày</string>
|
<string name="date">Ngày</string>
|
||||||
<string name="tags">Thẻ</string>
|
<string name="tags">Thẻ</string>
|
||||||
<string name="discard_all">Bỏ qua đã chọn</string>
|
<string name="approve">Phê duyệt</string>
|
||||||
<string name="approve_all">Phê duyệt đã chọn</string>
|
<string name="confirm">Xác nhận</string>
|
||||||
|
<string name="remember_my_choice_for">Ghi nhớ lựa chọn của tôi cho</string>
|
||||||
<string name="requests_message">Yêu cầu %1$s</string>
|
<string name="requests_message">Yêu cầu %1$s</string>
|
||||||
<string name="trust_excellent">Xuất sắc</string>
|
<string name="trust_excellent">Xuất sắc</string>
|
||||||
<string name="trust_good">Tốt</string>
|
<string name="trust_good">Tốt</string>
|
||||||
|
|||||||
@@ -614,8 +614,9 @@
|
|||||||
<string name="pubkey">公钥</string>
|
<string name="pubkey">公钥</string>
|
||||||
<string name="date">日期</string>
|
<string name="date">日期</string>
|
||||||
<string name="tags">标签</string>
|
<string name="tags">标签</string>
|
||||||
<string name="discard_all">拒绝所选</string>
|
<string name="approve">批准</string>
|
||||||
<string name="approve_all">批准所选</string>
|
<string name="confirm">确认</string>
|
||||||
|
<string name="remember_my_choice_for">为此记住我的选择</string>
|
||||||
<string name="requests_message">请求 %1$s</string>
|
<string name="requests_message">请求 %1$s</string>
|
||||||
<string name="trust_excellent">极佳</string>
|
<string name="trust_excellent">极佳</string>
|
||||||
<string name="trust_good">良好</string>
|
<string name="trust_good">良好</string>
|
||||||
|
|||||||
@@ -661,8 +661,9 @@
|
|||||||
<string name="pubkey">Pubkey</string>
|
<string name="pubkey">Pubkey</string>
|
||||||
<string name="date">Date</string>
|
<string name="date">Date</string>
|
||||||
<string name="tags">Tags</string>
|
<string name="tags">Tags</string>
|
||||||
<string name="discard_all">Discard selected</string>
|
<string name="approve">Approve</string>
|
||||||
<string name="approve_all">Approve selected</string>
|
<string name="confirm">Confirm</string>
|
||||||
|
<string name="remember_my_choice_for">Remember my choice for</string>
|
||||||
<string name="requests_message">Requests %1$s</string>
|
<string name="requests_message">Requests %1$s</string>
|
||||||
<string name="trust_excellent">Excellent</string>
|
<string name="trust_excellent">Excellent</string>
|
||||||
<string name="trust_good">Good</string>
|
<string name="trust_good">Good</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user