diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt index 6ea4cfa4..852c7375 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/MainScreen.kt @@ -62,7 +62,6 @@ import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.AmberBunkerRequest import com.greenart7c3.nostrsigner.models.IntentData import com.greenart7c3.nostrsigner.models.IntentResultType -import com.greenart7c3.nostrsigner.models.SignerType import com.greenart7c3.nostrsigner.service.crashreports.DisplayCrashMessages import com.greenart7c3.nostrsigner.ui.actions.AccountBackupScreen import com.greenart7c3.nostrsigner.ui.actions.AccountsBottomSheet @@ -341,8 +340,8 @@ fun MainScreen( Route.IncomingRequest.route, content = { val scrollState = rememberScrollState() - val isConnectEvent = intents.firstOrNull()?.type == SignerType.GET_PUBLIC_KEY - val modifier = if (intents.isEmpty() || packageName == null || destinationRoute != Route.IncomingRequest.route || !isConnectEvent) { + + val modifier = if (intents.isEmpty() || packageName == null || destinationRoute != Route.IncomingRequest.route) { Modifier .fillMaxSize() .padding(padding) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SeeDetailsScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SeeDetailsScreen.kt index 580e5149..c0401f90 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/SeeDetailsScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/SeeDetailsScreen.kt @@ -30,6 +30,7 @@ import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind import com.greenart7c3.nostrsigner.models.EventEncryptedDataKind import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.models.SignerType +import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind import com.greenart7c3.nostrsigner.service.BunkerRequestUtils import com.greenart7c3.nostrsigner.service.MultiEventScreenIntents import com.greenart7c3.nostrsigner.service.model.AmberEvent @@ -137,7 +138,13 @@ fun SeeDetailsScreen( intent.encryptedData.event.content } } else { - intent.encryptedData?.result ?: "" + if (intent.encryptedData is TagArrayEncryptedDataKind) { + intent.encryptedData.tagArray.joinToString(separator = ", ") { + "[${it.joinToString(separator = ", ") { tag -> "\"${tag}\"" }}]" + } + } else { + intent.encryptedData?.result ?: "" + } } } @@ -196,7 +203,13 @@ fun SeeDetailsScreen( bunkerRequest.encryptedData.event.content } } else { - bunkerRequest.encryptedData?.result ?: BunkerRequestUtils.getDataFromBunker(bunkerRequest.request) + if (bunkerRequest.encryptedData is TagArrayEncryptedDataKind) { + bunkerRequest.encryptedData.tagArray.joinToString(separator = ", ") { + "[${it.joinToString(separator = ", ") { tag -> "\"${tag}\"" }}]" + } + } else { + bunkerRequest.encryptedData?.result ?: BunkerRequestUtils.getDataFromBunker(bunkerRequest.request) + } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AcceptRejectButtons.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AcceptRejectButtons.kt index 91326bc1..0ad2e212 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AcceptRejectButtons.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AcceptRejectButtons.kt @@ -1,10 +1,9 @@ package com.greenart7c3.nostrsigner.ui.components import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.padding import androidx.compose.material3.ButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -18,24 +17,25 @@ fun AcceptRejectButtons( onAccept: () -> Unit, onReject: () -> Unit, ) { - Column( + Row( Modifier - .fillMaxWidth(), - Arrangement.Center, + .fillMaxWidth() + .padding(vertical = 16.dp), + Arrangement.spacedBy(8.dp), ) { AmberButton( - modifier = Modifier.fillMaxWidth(), - onClick = onAccept, - text = stringResource(R.string.accept), - ) - - Spacer(modifier = Modifier.width(20.dp)) - AmberButton( + Modifier.weight(1f), onClick = onReject, colors = ButtonDefaults.buttonColors( containerColor = Color(0xFFFF6B00), ), text = stringResource(R.string.reject), ) + + AmberButton( + Modifier.weight(1f), + onClick = onAccept, + text = stringResource(R.string.accept), + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberButton.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberButton.kt index dd387fdd..e552855f 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberButton.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberButton.kt @@ -51,7 +51,7 @@ fun AmberButton( CompositionLocalProvider( LocalDensity provides Density( LocalDensity.current.density, - 1.5f, + 1f, ), ) { Text( @@ -59,7 +59,7 @@ fun AmberButton( color = textColor, textAlign = textAlign, maxLines = maxLines, - fontWeight = FontWeight.Normal, + fontWeight = FontWeight.Bold, ) } } @@ -96,7 +96,7 @@ fun AmberElevatedButton( CompositionLocalProvider( LocalDensity provides Density( LocalDensity.current.density, - 1.5f, + 1f, ), ) { Text( @@ -104,7 +104,7 @@ fun AmberElevatedButton( color = textColor, textAlign = textAlign, maxLines = maxLines, - fontWeight = FontWeight.Normal, + fontWeight = FontWeight.Bold, ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberTopAppBar.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberTopAppBar.kt index db5a0937..f8cf5cbc 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberTopAppBar.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/AmberTopAppBar.kt @@ -40,7 +40,6 @@ import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.AmberBunkerRequest import com.greenart7c3.nostrsigner.models.IntentData -import com.greenart7c3.nostrsigner.models.SignerType import com.greenart7c3.nostrsigner.service.toShortenHex import com.greenart7c3.nostrsigner.ui.navigation.Route import com.greenart7c3.nostrsigner.ui.navigation.routes @@ -63,8 +62,7 @@ fun AmberTopAppBar( bunkerRequests: List, packageName: String?, ) { - val isConnectEvent = intents.firstOrNull()?.type == SignerType.GET_PUBLIC_KEY - if (intents.isEmpty() || packageName == null || destinationRoute != Route.IncomingRequest.route || !isConnectEvent) { + if (intents.isEmpty() || packageName == null || destinationRoute != Route.IncomingRequest.route) { if (destinationRoute != "login" && destinationRoute != "create" && destinationRoute != "loginPage") { CenterAlignedTopAppBar( actions = { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerGetPubKeyScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerGetPubKeyScreen.kt index af86fbf0..29f8acee 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerGetPubKeyScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerGetPubKeyScreen.kt @@ -1,34 +1,26 @@ package com.greenart7c3.nostrsigner.ui.components -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.greenart7c3.nostrsigner.R -import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.ui.RememberType @Composable fun BunkerGetPubKeyScreen( modifier: Modifier, - account: Account, applicationName: String, onAccept: (List?, Int, Boolean?, RememberType) -> Unit, onReject: (RememberType) -> Unit, @@ -38,8 +30,6 @@ fun BunkerGetPubKeyScreen( Column( modifier, ) { - ProfilePicture(account) - Text( modifier = Modifier.fillMaxWidth(), text = applicationName, @@ -54,42 +44,24 @@ fun BunkerGetPubKeyScreen( Spacer(modifier = Modifier.weight(1f)) - Column( - Modifier - .fillMaxWidth() - .padding(10.dp), - Arrangement.Center, - Alignment.CenterHorizontally, - ) { - RememberMyChoice( - alwaysShow = true, - shouldRunAcceptOrReject = null, - onAccept = {}, - onReject = onReject, - onChanged = { - rememberType = it - }, - packageName = null, - ) + RememberMyChoice( + alwaysShow = true, + shouldRunAcceptOrReject = null, + onAccept = {}, + onReject = onReject, + onChanged = { + rememberType = it + }, + packageName = null, + ) - AmberButton( - modifier = Modifier.padding(vertical = 20.dp), - onClick = { - onAccept(null, 1, null, rememberType) - }, - text = stringResource(R.string.grant_permissions), - ) - - AmberButton( - modifier = Modifier.padding(vertical = 20.dp), - onClick = { - onReject(rememberType) - }, - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFFFF6B00), - ), - text = stringResource(R.string.reject), - ) - } + AcceptRejectButtons( + onAccept = { + onAccept(null, 1, null, rememberType) + }, + onReject = { + onReject(rememberType) + }, + ) } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerPingScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerPingScreen.kt index dbf6866a..998571bc 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerPingScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerPingScreen.kt @@ -22,12 +22,10 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.greenart7c3.nostrsigner.R -import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.ui.RememberType @Composable fun BunkerPingScreen( - account: Account, modifier: Modifier, shouldRunOnAccept: Boolean?, appName: String, @@ -41,8 +39,6 @@ fun BunkerPingScreen( Column( modifier, ) { - ProfilePicture(account) - val message = stringResource(R.string.ping) Text( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerSingleEventHomeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerSingleEventHomeScreen.kt index 45e5924d..b43d29af 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerSingleEventHomeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerSingleEventHomeScreen.kt @@ -105,7 +105,6 @@ fun BunkerSingleEventHomeScreen( BunkerPingScreen( modifier = modifier, - account = account, appName = appName, shouldRunOnAccept = acceptOrReject, onAccept = { @@ -300,7 +299,6 @@ fun BunkerSingleEventHomeScreen( is BunkerRequestGetPublicKey -> { BunkerGetPubKeyScreen( modifier = modifier, - account = account, applicationName = appName, onAccept = { permissions, signPolicy, closeApplication, rememberType -> val result = account.hexKey @@ -362,7 +360,6 @@ fun BunkerSingleEventHomeScreen( } BunkerEncryptDecryptData( - account = account, modifier = modifier, encryptedData = bunkerRequest.encryptedData, shouldRunOnAccept = acceptOrReject, @@ -426,7 +423,6 @@ fun BunkerSingleEventHomeScreen( } BunkerEncryptDecryptData( - account = account, modifier = modifier, encryptedData = bunkerRequest.encryptedData, shouldRunOnAccept = acceptOrReject, @@ -492,7 +488,6 @@ fun BunkerSingleEventHomeScreen( } BunkerEncryptDecryptData( - account = account, modifier = modifier, encryptedData = bunkerRequest.encryptedData, shouldRunOnAccept = acceptOrReject, @@ -558,7 +553,6 @@ fun BunkerSingleEventHomeScreen( } BunkerEncryptDecryptData( - account = account, modifier = modifier, encryptedData = bunkerRequest.encryptedData, shouldRunOnAccept = acceptOrReject, @@ -710,7 +704,6 @@ fun BunkerSingleEventHomeScreen( } BunkerEncryptDecryptData( - account = account, modifier = modifier, encryptedData = bunkerRequest.encryptedData, shouldRunOnAccept = acceptOrReject, @@ -772,12 +765,10 @@ fun BunkerSingleEventHomeScreen( } BunkerSignMessage( - account = account, modifier = modifier, content = bunkerRequest.request.params.first(), shouldRunOnAccept = acceptOrReject, appName = appName, - type = type, onAccept = { Amber.instance.applicationIOScope.launch(Dispatchers.IO) { val result = account.signString(bunkerRequest.request.params.first()) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EncryptDecryptData.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EncryptDecryptData.kt index 98092728..f55ebd84 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EncryptDecryptData.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EncryptDecryptData.kt @@ -24,17 +24,16 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.greenart7c3.nostrsigner.Amber import com.greenart7c3.nostrsigner.R -import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind import com.greenart7c3.nostrsigner.models.EncryptedDataKind import com.greenart7c3.nostrsigner.models.EventEncryptedDataKind import com.greenart7c3.nostrsigner.models.Permission +import com.greenart7c3.nostrsigner.models.PrivateZapEncryptedDataKind import com.greenart7c3.nostrsigner.models.SignerType import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind import com.greenart7c3.nostrsigner.ui.RememberType @@ -42,12 +41,200 @@ import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent @Composable fun EncryptDecryptData( - account: Account, modifier: Modifier, encryptedData: EncryptedDataKind?, shouldRunOnAccept: Boolean?, packageName: String?, - applicationName: String?, + type: SignerType, + onAccept: (RememberType) -> Unit, + onReject: (RememberType) -> Unit, +) { + var rememberType by remember { + mutableStateOf(RememberType.NEVER) + } + + Column( + modifier, + ) { + LocalAppIcon(packageName) + + val text = if (type.name.contains("ENCRYPT")) { + when (encryptedData) { + is EventEncryptedDataKind -> { + val permission = Permission("sign_event", encryptedData.event.kind) + stringResource(R.string.wants_to_encrypt_with, permission.toLocalizedString(Amber.instance), type.name.split("_").first()) + } + + is TagArrayEncryptedDataKind -> { + stringResource(R.string.wants_to_encrypt_this_list_of_tags_with, type.name.split("_").first()) + } + + else -> stringResource(R.string.wants_to_encrypt_this_text_with, type.name.split("_").first()) + } + } else { + when (encryptedData) { + is EventEncryptedDataKind -> { + val permission = Permission("sign_event", encryptedData.event.kind) + stringResource(R.string.wants_to_read_from_encrypted_content, permission.toLocalizedString(Amber.instance), type.name.split("_").first()) + } + + is TagArrayEncryptedDataKind -> { + stringResource(R.string.wants_to_read_this_list_of_tags_from_encrypted_content, type.name.split("_").first()) + } + + is PrivateZapEncryptedDataKind -> { + stringResource(R.string.wants_you_to, stringResource(R.string.decrypt_zap_event)) + } + + else -> stringResource(R.string.wants_to_read_this_text_from_encrypted_content, type.name.split("_").first()) + } + } + + Text( + text.trim().capitalize(Locale.current), + fontSize = 18.sp, + ) + Spacer(Modifier.size(4.dp)) + + Card( + modifier = Modifier + .fillMaxWidth() + .weight(1f), + colors = CardDefaults.cardColors().copy( + containerColor = MaterialTheme.colorScheme.background, + ), + ) { + Column { + if (encryptedData is EventEncryptedDataKind) { + if (encryptedData.sealEncryptedDataKind != null) { + if (encryptedData.sealEncryptedDataKind is EventEncryptedDataKind) { + val content = if (encryptedData.sealEncryptedDataKind.event.kind == 22242) encryptedData.sealEncryptedDataKind.event.relay() else encryptedData.sealEncryptedDataKind.event.content + if (encryptedData.event.kind == ContactListEvent.KIND) { + ContactListDetail( + title = stringResource(R.string.following), + text = "${encryptedData.event.verifiedFollowKeySet().size}", + ) + } else { + Text( + content, + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + ) + } + } + } else { + val content = if (encryptedData.event.kind == 22242) encryptedData.event.relay() else encryptedData.event.content + if (encryptedData.event.kind == ContactListEvent.KIND) { + ContactListDetail( + title = stringResource(R.string.following), + text = "${encryptedData.event.verifiedFollowKeySet().size}", + ) + } else { + Text( + content, + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + ) + } + } + } else { + if (encryptedData is TagArrayEncryptedDataKind) { + val mappedTags = encryptedData.tagArray.mapNotNull { tag -> + if (tag.isEmpty()) return@mapNotNull null + + TagItem( + name = tag.first(), + values = tag.drop(1), + ) + } + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .padding(top = 8.dp), + ) { + items(mappedTags) { item -> + Column( + modifier = Modifier + .fillMaxWidth(), + ) { + Text( + text = item.name, + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 4.dp), + ) + + item.values.forEach { value -> + Text( + fontSize = 16.sp, + text = value, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 2.dp), + ) + } + } + + HorizontalDivider( + color = MaterialTheme.colorScheme.primary, + ) + } + } + } else { + val content = if (type.name.contains("ENCRYPT") && encryptedData is ClearTextEncryptedDataKind) { + encryptedData.text + } else { + encryptedData?.result ?: "" + } + Text( + content, + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + ) + } + } + } + } + + if (encryptedData !is TagArrayEncryptedDataKind) Spacer(modifier = Modifier.weight(1f)) + + RememberMyChoice( + shouldRunOnAccept, + packageName, + false, + onAccept, + onReject, + ) { + rememberType = it + } + + AcceptRejectButtons( + onAccept = { + onAccept(rememberType) + }, + onReject = { + onReject(rememberType) + }, + ) + } +} + +data class TagItem( + val name: String, + val values: List, +) + +@Composable +fun BunkerEncryptDecryptData( + modifier: Modifier, + encryptedData: EncryptedDataKind?, + shouldRunOnAccept: Boolean?, appName: String, type: SignerType, onAccept: (RememberType) -> Unit, @@ -60,26 +247,11 @@ fun EncryptDecryptData( Column( modifier, ) { - ProfilePicture(account) - - packageName?.let { - Text( - modifier = Modifier - .fillMaxWidth(), - text = it, - fontWeight = FontWeight.Bold, - fontSize = 18.sp, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.size(4.dp)) - } - Text( buildAnnotatedString { withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append(applicationName ?: appName) + append(appName) } - if (type.name.contains("ENCRYPT")) { when (encryptedData) { is EventEncryptedDataKind -> { @@ -220,153 +392,6 @@ fun EncryptDecryptData( if (encryptedData !is TagArrayEncryptedDataKind) Spacer(modifier = Modifier.weight(1f)) - RememberMyChoice( - shouldRunOnAccept, - packageName, - false, - onAccept, - onReject, - ) { - rememberType = it - } - - AcceptRejectButtons( - onAccept = { - onAccept(rememberType) - }, - onReject = { - onReject(rememberType) - }, - ) - } -} - -data class TagItem( - val name: String, - val values: List, -) - -@Composable -fun BunkerEncryptDecryptData( - account: Account, - modifier: Modifier, - encryptedData: EncryptedDataKind?, - shouldRunOnAccept: Boolean?, - appName: String, - type: SignerType, - onAccept: (RememberType) -> Unit, - onReject: (RememberType) -> Unit, -) { - var rememberType by remember { - mutableStateOf(RememberType.NEVER) - } - - Column( - modifier, - ) { - ProfilePicture(account) - - Text( - buildAnnotatedString { - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append(appName) - } - if (type.name.contains("ENCRYPT")) { - when (encryptedData) { - is EventEncryptedDataKind -> { - val permission = Permission("sign_event", encryptedData.event.kind) - append(stringResource(R.string.wants_to_encrypt_with, permission.toLocalizedString(Amber.instance), type.name.split("_").first())) - } - - is TagArrayEncryptedDataKind -> { - append(stringResource(R.string.wants_to_encrypt_this_list_of_tags_with, type.name.split("_").first())) - } - - else -> append(stringResource(R.string.wants_to_encrypt_this_text_with, type.name.split("_").first())) - } - } else { - when (encryptedData) { - is EventEncryptedDataKind -> { - val permission = Permission("sign_event", encryptedData.event.kind) - append(stringResource(R.string.wants_to_read_from_encrypted_content, permission.toLocalizedString(Amber.instance), type.name.split("_").first())) - } - - is TagArrayEncryptedDataKind -> { - append(stringResource(R.string.wants_to_read_this_list_of_tags_from_encrypted_content, type.name.split("_").first())) - } - - else -> append(stringResource(R.string.wants_to_read_this_text_from_encrypted_content, type.name.split("_").first())) - } - } - }, - fontSize = 18.sp, - ) - Spacer(Modifier.size(4.dp)) - - Card( - modifier = Modifier - .fillMaxWidth(), - ) { - Column(Modifier.padding(6.dp)) { - Text( - stringResource(R.string.content).capitalize(Locale.current), - fontWeight = FontWeight.Bold, - ) - - if (encryptedData is EventEncryptedDataKind) { - if (encryptedData.sealEncryptedDataKind != null) { - if (encryptedData.sealEncryptedDataKind is EventEncryptedDataKind) { - val content = if (encryptedData.sealEncryptedDataKind.event.kind == 22242) encryptedData.sealEncryptedDataKind.event.relay() else encryptedData.sealEncryptedDataKind.event.content - if (encryptedData.event.kind == ContactListEvent.KIND) { - ContactListDetail( - title = stringResource(R.string.following), - text = "${encryptedData.event.verifiedFollowKeySet().size}", - ) - } else { - Text( - content, - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - ) - } - } - } else { - val content = if (encryptedData.event.kind == 22242) encryptedData.event.relay() else encryptedData.event.content - if (encryptedData.event.kind == ContactListEvent.KIND) { - ContactListDetail( - title = stringResource(R.string.following), - text = "${encryptedData.event.verifiedFollowKeySet().size}", - ) - } else { - Text( - content, - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - ) - } - } - } else { - val content = if (type.name.contains("ENCRYPT") && encryptedData is ClearTextEncryptedDataKind) { - encryptedData.text - } else if (encryptedData is TagArrayEncryptedDataKind) { - encryptedData.tagArray.map { it.toList() }.toList().toString() - } else { - encryptedData?.result ?: "" - } - Text( - content, - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - ) - } - } - } - - Spacer(modifier = Modifier.weight(1f)) - RememberMyChoice( shouldRunOnAccept, null, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EventData.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EventData.kt index e831d410..06c51bca 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EventData.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EventData.kt @@ -1,28 +1,36 @@ package com.greenart7c3.nostrsigner.ui.components +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material3.Card +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.ModalBottomSheetProperties +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -30,22 +38,92 @@ import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.models.SignerType -import com.greenart7c3.nostrsigner.service.model.AmberEvent +import com.greenart7c3.nostrsigner.models.TimeUtils +import com.greenart7c3.nostrsigner.service.toShortenHex import com.greenart7c3.nostrsigner.ui.RememberType +import com.greenart7c3.nostrsigner.ui.verticalScrollbar import com.vitorpamplona.quartz.nip01Core.core.Event -import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EventDetailModal( + event: Event, + permission: Permission, + onDismiss: () -> Unit, +) { + val sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true, + ) + ModalBottomSheet( + sheetGesturesEnabled = false, + properties = ModalBottomSheetProperties( + shouldDismissOnBackPress = false, + shouldDismissOnClickOutside = false, + ), + sheetMaxWidth = Int.MAX_VALUE.dp, + sheetState = sheetState, + onDismissRequest = onDismiss, + ) { + Scaffold( + bottomBar = { + Box( + Modifier.background(MaterialTheme.colorScheme.primary), + ) { + IconRow( + center = true, + title = stringResource(R.string.go_back), + icon = ImageVector.vectorResource(R.drawable.back), + onClick = onDismiss, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + }, + ) { + val scrollState = rememberScrollState() + Column( + Modifier + .padding(it) + .verticalScrollbar(scrollState) + .verticalScroll(scrollState), + ) { + EventSection( + stringResource(R.string.kind), + "${event.kind} - ${permission.toLocalizedString(LocalContext.current)}", + ) + EventSection( + stringResource(R.string.pubkey), + event.pubKey.toShortenHex(), + ) + EventSection( + stringResource(R.string.date), + TimeUtils.formatLongToCustomDateTimeWithSeconds(event.createdAt), + ) + if (event.content.isNotEmpty()) { + EventSection( + stringResource(R.string.content), + event.content, + ) + } + if (event.tags.isNotEmpty()) { + TagsSection( + label = stringResource(R.string.tags), + tags = event.tags, + onCopy = { + }, + ) + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) @Composable fun EventData( - account: Account, modifier: Modifier, shouldAcceptOrReject: Boolean?, packageName: String?, - appName: String, - applicationName: String?, event: Event, - rawJson: String, - type: SignerType, onAccept: (RememberType) -> Unit, onReject: (RememberType) -> Unit, ) { @@ -60,76 +138,30 @@ fun EventData( Column( modifier, ) { - ProfilePicture(account) + LocalAppIcon(packageName) val permission = Permission("sign_event", event.kind) val text = stringResource(R.string.wants_you_to_sign_a, permission.toLocalizedString(context)) - packageName?.let { - Text( - modifier = Modifier - .fillMaxWidth(), - text = it, - fontWeight = FontWeight.Bold, - fontSize = 18.sp, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.size(4.dp)) - } Text( - buildAnnotatedString { - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append(applicationName ?: appName) - } - append(" $text") - }, + text.capitalize(Locale.current), fontSize = 18.sp, ) Spacer(Modifier.size(4.dp)) - val content = if (event.kind == 22242) AmberEvent.relay(event) else event.content - if (content.isNotBlank()) { - key("event-data-card") { - Card( - modifier = Modifier - .fillMaxWidth(), - ) { - Column(Modifier.padding(16.dp)) { - if (event is ContactListEvent) { - ContactListDetail( - title = stringResource(R.string.following), - text = "${event.verifiedFollowKeySet().size}", - ) - ContactListDetail( - title = stringResource(R.string.relays_text), - text = "${event.relays()?.keys?.size ?: 0}", - ) - } else { - Text( - "Event content", - fontWeight = FontWeight.Bold, - ) - Text( - content, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - ) - } - } - } - } - } - RawJsonButton( onCLick = { showMore = !showMore }, - if (!showMore) stringResource(R.string.show_details) else stringResource(R.string.hide_details), + stringResource(R.string.show_details), ) if (showMore) { - RawJson(rawJson, "", Modifier.height(200.dp), type = type) + EventDetailModal( + event = event, + permission = permission, + onDismiss = { + showMore = false + }, + ) } else { Spacer(modifier = Modifier.weight(1f)) } @@ -178,8 +210,6 @@ fun BunkerEventData( Column( modifier, ) { - ProfilePicture(account) - val permission = Permission("sign_event", event.kind) val text = stringResource(R.string.wants_you_to_sign_a, permission.toLocalizedString(context)) Text( @@ -193,42 +223,6 @@ fun BunkerEventData( ) Spacer(Modifier.size(4.dp)) - val content = if (event.kind == 22242) AmberEvent.relay(event) else event.content - if (content.isNotBlank()) { - key("event-data-card") { - Card( - modifier = Modifier - .fillMaxWidth(), - ) { - Column(Modifier.padding(16.dp)) { - if (event is ContactListEvent) { - ContactListDetail( - title = stringResource(R.string.following), - text = "${event.verifiedFollowKeySet().size}", - ) - ContactListDetail( - title = stringResource(R.string.relays_text), - text = "${event.relays()?.keys?.size ?: 0}", - ) - } else { - Text( - "Event content", - fontWeight = FontWeight.Bold, - ) - Text( - content, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - ) - } - } - } - } - } - RawJsonButton( onCLick = { showMore = !showMore @@ -236,7 +230,13 @@ fun BunkerEventData( if (!showMore) stringResource(R.string.show_details) else stringResource(R.string.hide_details), ) if (showMore) { - RawJson(rawJson, "", Modifier.height(200.dp), type = type) + EventDetailModal( + event = event, + permission = permission, + onDismiss = { + showMore = false + }, + ) } else { Spacer(modifier = Modifier.weight(1f)) } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EventSection.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EventSection.kt new file mode 100644 index 00000000..107d48c9 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/EventSection.kt @@ -0,0 +1,63 @@ +package com.greenart7c3.nostrsigner.ui.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ContentCopy +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.greenart7c3.nostrsigner.R + +@Composable +fun EventSection( + label: String, + displayValue: String, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top, + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = label, + style = MaterialTheme.typography.bodyLarge.copy(fontSize = 16.sp, lineHeight = 20.sp), + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, + ) + Text( + modifier = Modifier.padding(top = 12.dp), + text = displayValue, + style = MaterialTheme.typography.bodyMedium.copy( + lineHeight = 24.sp, + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Bold, + ) + } + Icon( + modifier = Modifier + .size(16.dp) + .clickable { }, + imageVector = Icons.Default.ContentCopy, + contentDescription = stringResource(id = R.string.copy), + ) + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentSingleEventHomeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentSingleEventHomeScreen.kt index 2ba204d8..91563b65 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentSingleEventHomeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentSingleEventHomeScreen.kt @@ -119,14 +119,10 @@ fun IntentSingleEventHomeScreen( val acceptOrReject = IntentUtils.isRemembered(applicationEntity?.application?.signPolicy, permission) SignMessage( - account = account, modifier = modifier, content = intentData.data, shouldRunOnAccept = acceptOrReject, packageName = packageName, - applicationName = applicationName, - appName = appName, - type = intentData.type, onAccept = { Amber.instance.applicationIOScope.launch(Dispatchers.IO) { val result = account.signString(intentData.data) @@ -182,13 +178,10 @@ fun IntentSingleEventHomeScreen( val acceptOrReject = IntentUtils.isRemembered(applicationEntity?.application?.signPolicy, permission) EncryptDecryptData( - account = account, modifier = modifier, encryptedData = intentData.encryptedData, shouldRunOnAccept = acceptOrReject, packageName = packageName, - applicationName = applicationName, - appName = appName, type = intentData.type, onAccept = { val result = @@ -259,15 +252,10 @@ fun IntentSingleEventHomeScreen( val acceptOrReject = IntentUtils.isRemembered(applicationEntity?.application?.signPolicy, permission) EventData( - account = account, modifier = modifier, shouldAcceptOrReject = acceptOrReject, packageName = packageName, - appName = appName, - applicationName = applicationName, event = event, - rawJson = event.toJson(), - type = intentData.type, onAccept = { if (intentData.unsignedEventKey.isNotBlank() && intentData.unsignedEventKey != account.hexKey && !isPrivateEvent(event.kind, event.tags)) { accountStateViewModel.toast( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LocalAppIcon.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LocalAppIcon.kt new file mode 100644 index 00000000..da4b74d8 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LocalAppIcon.kt @@ -0,0 +1,62 @@ +package com.greenart7c3.nostrsigner.ui.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.graphics.drawable.toBitmap + +@Composable +fun LocalAppIcon(packageName: String?) { + packageName?.let { + val appDisplayInfo = rememberAppDisplayInfo(packageName) + Column( + modifier = Modifier.padding(vertical = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + if (appDisplayInfo.icon != null) { + Image( + modifier = Modifier + .size(48.dp) + .clip(MaterialTheme.shapes.small), + bitmap = appDisplayInfo.icon.toBitmap().asImageBitmap(), + contentDescription = appDisplayInfo.name, + contentScale = ContentScale.Crop, + ) + } + + Text( + text = appDisplayInfo.name, + style = MaterialTheme.typography.titleLarge.copy( + fontSize = 18.sp, + lineHeight = 24.sp, + ), + fontWeight = FontWeight.Bold, + ) + + Text( + modifier = Modifier + .fillMaxWidth(), + text = packageName, + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + textAlign = TextAlign.Center, + ) + } + } +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LoginWithPubKey.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LoginWithPubKey.kt index 8b453ade..342f7b35 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LoginWithPubKey.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/LoginWithPubKey.kt @@ -31,7 +31,6 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -55,7 +54,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.graphics.drawable.toBitmap import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_MEDIUM_LOWER_BOUND import coil3.compose.AsyncImage import com.greenart7c3.nostrsigner.Amber import com.greenart7c3.nostrsigner.BuildFlavorChecker @@ -68,52 +66,6 @@ import com.greenart7c3.nostrsigner.ui.RememberType import com.greenart7c3.nostrsigner.ui.navigation.Route import com.greenart7c3.nostrsigner.ui.theme.fromHex -@Composable -fun ProfilePicture(account: Account) { - val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass - if (windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND)) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - val profileUrl by account.picture.collectAsStateWithLifecycle() - if (profileUrl.isNotBlank() && !BuildFlavorChecker.isOfflineFlavor()) { - AsyncImage( - profileUrl, - Route.Accounts.route, - Modifier - .clip( - RoundedCornerShape(50), - ) - .height(120.dp) - .width(120.dp), - ) - } else { - Icon( - Icons.Outlined.Person, - Route.Accounts.route, - modifier = Modifier - .border( - 2.dp, - Color.fromHex(account.hexKey.slice(0..5)), - CircleShape, - ) - .height(120.dp) - .width(120.dp), - ) - } - val name by account.name.collectAsStateWithLifecycle() - Text( - name.ifBlank { account.npub.toShortenHex() }, - fontWeight = FontWeight.Bold, - fontSize = 18.sp, - textAlign = TextAlign.Center, - ) - } - } -} - @Composable fun ProfilePictureIcon(account: Account) { val profileUrl by account.picture.collectAsStateWithLifecycle() diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MultiEventHomeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MultiEventHomeScreen.kt index c9be8514..f036eb6d 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MultiEventHomeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MultiEventHomeScreen.kt @@ -10,6 +10,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -33,6 +35,7 @@ import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.toLowerCase @@ -170,127 +173,150 @@ fun IntentMultiEventHomeScreen( } } - val appName = ApplicationNameCache.names["$localAccount-$key"] ?: key.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( modifier, ) { + LocalAppIcon(packageName) + Text( - stringResource(R.string.is_requiring_some_permissions_please_review_them, appName), + stringResource(R.string.is_requiring_some_permissions_please_review_them2), Modifier .fillMaxWidth() .padding(bottom = 20.dp), ) - groupedEventEncryptedData.toList().forEachIndexed { index, it -> - PermissionCard( - context = context, - acceptEventsGroup = acceptedGroupedEventEncryptedData, - index = index, - item = it, - onDetailsClick = { - MultiEventScreenIntents.intents = it - MultiEventScreenIntents.appName = appName - navController.navigate(Route.SeeDetails.route) - }, - ) - } - groupedTextEncryptedData.toList().forEachIndexed { index, it -> - PermissionCard( - context = context, - acceptEventsGroup = acceptedGroupedTextEncryptedData, - index = index, - item = it, - onDetailsClick = { - MultiEventScreenIntents.intents = it - MultiEventScreenIntents.appName = appName - navController.navigate(Route.SeeDetails.route) - }, - ) - } - groupedTagArrayEncryptedData.toList().forEachIndexed { index, it -> - PermissionCard( - context = context, - acceptEventsGroup = acceptedGroupedTagArrayEncryptedData, - index = index, - item = it, - onDetailsClick = { - MultiEventScreenIntents.intents = it - MultiEventScreenIntents.appName = appName - navController.navigate(Route.SeeDetails.route) - }, - ) - } - groupedPrivateZapEncryptedDataKind.toList().forEachIndexed { index, it -> - PermissionCard( - context = context, - acceptEventsGroup = acceptedGroupedPrivateZapEncryptedDataKind, - index = index, - item = it, - onDetailsClick = { - MultiEventScreenIntents.intents = it - MultiEventScreenIntents.appName = appName - navController.navigate(Route.SeeDetails.route) - }, - ) - } - groupedOthers.toList().forEachIndexed { index, it -> - PermissionCard( - context = context, - acceptEventsGroup = acceptEventsGroupOthers, - index = index, - item = it, - onDetailsClick = { - MultiEventScreenIntents.intents = it - MultiEventScreenIntents.appName = appName - navController.navigate(Route.SeeDetails.route) - }, - ) - } - groupedEvents.toList().forEachIndexed { index, it -> - PermissionCard( - context = context, - acceptEventsGroup = acceptEventsGroup2, - index = index, - item = it, - onDetailsClick = { - MultiEventScreenIntents.intents = it - MultiEventScreenIntents.appName = appName - navController.navigate(Route.SeeDetails.route) - }, - ) + Column( + Modifier + .weight(1f) + .verticalScroll(rememberScrollState()), + ) { + groupedEventEncryptedData.toList().forEachIndexed { index, it -> + PermissionCard( + context = context, + acceptEventsGroup = acceptedGroupedEventEncryptedData, + index = index, + item = it, + onDetailsClick = { + MultiEventScreenIntents.intents = it + MultiEventScreenIntents.appName = appName + navController.navigate(Route.SeeDetails.route) + }, + ) + } + groupedTextEncryptedData.toList().forEachIndexed { index, it -> + PermissionCard( + context = context, + acceptEventsGroup = acceptedGroupedTextEncryptedData, + index = index, + item = it, + onDetailsClick = { + MultiEventScreenIntents.intents = it + MultiEventScreenIntents.appName = appName + navController.navigate(Route.SeeDetails.route) + }, + ) + } + groupedTagArrayEncryptedData.toList().forEachIndexed { index, it -> + PermissionCard( + context = context, + acceptEventsGroup = acceptedGroupedTagArrayEncryptedData, + index = index, + item = it, + onDetailsClick = { + MultiEventScreenIntents.intents = it + MultiEventScreenIntents.appName = appName + navController.navigate(Route.SeeDetails.route) + }, + ) + } + groupedPrivateZapEncryptedDataKind.toList().forEachIndexed { index, it -> + PermissionCard( + context = context, + acceptEventsGroup = acceptedGroupedPrivateZapEncryptedDataKind, + index = index, + item = it, + onDetailsClick = { + MultiEventScreenIntents.intents = it + MultiEventScreenIntents.appName = appName + navController.navigate(Route.SeeDetails.route) + }, + ) + } + groupedOthers.toList().forEachIndexed { index, it -> + PermissionCard( + context = context, + acceptEventsGroup = acceptEventsGroupOthers, + index = index, + item = it, + onDetailsClick = { + MultiEventScreenIntents.intents = it + MultiEventScreenIntents.appName = appName + navController.navigate(Route.SeeDetails.route) + }, + ) + } + groupedEvents.toList().forEachIndexed { index, it -> + PermissionCard( + context = context, + acceptEventsGroup = acceptEventsGroup2, + index = index, + item = it, + onDetailsClick = { + MultiEventScreenIntents.intents = it + MultiEventScreenIntents.appName = appName + navController.navigate(Route.SeeDetails.route) + }, + ) + } } + RememberMyChoice( + alwaysShow = true, + shouldRunAcceptOrReject = null, + onAccept = {}, + onReject = {}, + onChanged = { + rememberType = it + intents.forEach { intent -> + intent.rememberType.value = rememberType + } + }, + packageName = packageName, + ) + Row( - Modifier.padding(vertical = 40.dp), + Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - RememberMyChoice( - alwaysShow = true, - shouldRunAcceptOrReject = null, - onAccept = {}, - onReject = {}, - onChanged = { - rememberType = it - intents.forEach { intent -> - intent.rememberType.value = rememberType - } - }, - packageName = packageName, - ) - } - - AmberButton( - Modifier.padding(bottom = 40.dp), - text = stringResource(R.string.approve_selected), - onClick = { - onLoading(true) - Amber.instance.applicationIOScope.launch(Dispatchers.IO) { - try { - val results = mutableListOf() + AmberButton( + Modifier.weight(1f), + colors = ButtonDefaults.buttonColors().copy( + containerColor = orange, + ), + onClick = { + Amber.instance.applicationIOScope.launch { var closeApp = true - onRemoveIntentData(intents, IntentResultType.REMOVE) - for (intentData in intents) { val thisAccount = if (intentData.currentAccount.isNotBlank()) { @@ -303,13 +329,11 @@ fun IntentMultiEventHomeScreen( } ?: continue val localKey = packageName ?: continue - val database = Amber.instance.getDatabase(thisAccount.npub) - val historyDatabase = Amber.instance.getHistoryDatabase(thisAccount.npub) - val savedApplication = database.dao().getByKey(localKey) - val application = - savedApplication ?: ApplicationWithPermissions( + database + .dao() + .getByKey(localKey) ?: ApplicationWithPermissions( application = ApplicationEntity( localKey, "", @@ -329,235 +353,241 @@ fun IntentMultiEventHomeScreen( permissions = mutableListOf(), ) + if (intentData.rememberType.value != RememberType.NEVER && intentData.checked.value) { + AmberUtils.acceptOrRejectPermission( + application, + localKey, + intentData.type, + null, + false, + intentData.rememberType.value, + thisAccount, + ) + } + if (!application.application.closeApplication) { closeApp = false } - - if (intentData.type == SignerType.SIGN_EVENT) { - val localEvent = intentData.event!! - - if (intentData.rememberType.value != RememberType.NEVER && intentData.checked.value) { - AmberUtils.acceptOrRejectPermission( - application, - localKey, - intentData.type, - localEvent.kind, - true, - intentData.rememberType.value, - thisAccount, - ) - } - - database.dao().insertApplicationWithPermissions(application) - - historyDatabase.dao().addHistory( - HistoryEntity( - 0, - localKey, - intentData.type.toString(), - localEvent.kind, - TimeUtils.now(), - intentData.checked.value, - ), - thisAccount.npub, + } + sendRejectIntent( + results = intents.map { + Result( + null, + signature = null, + result = null, + id = it.id, + rejected = true, ) + }.toMutableList(), + ) + finishActivity(closeApp) + } + }, + text = stringResource(R.string.discard_all), + ) - if (intentData.checked.value) { - results.add( - Result( - null, - signature = if (localEvent is LnZapRequestEvent && - localEvent.tags.any { tag -> - tag.any { t -> t == "anon" } - } - ) { - localEvent.toJson() - } else { - localEvent.sig - }, - result = if (localEvent is LnZapRequestEvent && - localEvent.tags.any { tag -> - tag.any { t -> t == "anon" } - } - ) { - localEvent.toJson() - } else { - localEvent.sig - }, - id = intentData.id, - rejected = null, + AmberButton( + Modifier.weight(1f), + text = stringResource(R.string.approve_all), + onClick = { + onLoading(true) + Amber.instance.applicationIOScope.launch(Dispatchers.IO) { + try { + val results = mutableListOf() + var closeApp = true + + onRemoveIntentData(intents, IntentResultType.REMOVE) + + for (intentData in intents) { + val thisAccount = + if (intentData.currentAccount.isNotBlank()) { + LocalPreferences.loadFromEncryptedStorage( + context, + intentData.currentAccount, + ) + } else { + accountParam + } ?: continue + + val localKey = packageName ?: continue + + val database = Amber.instance.getDatabase(thisAccount.npub) + val historyDatabase = Amber.instance.getHistoryDatabase(thisAccount.npub) + val savedApplication = database.dao().getByKey(localKey) + + val application = + savedApplication ?: ApplicationWithPermissions( + application = ApplicationEntity( + localKey, + "", + listOf(), + "", + "", + "", + thisAccount.hexKey, + true, + "", + false, + thisAccount.signPolicy, + true, + 0L, + lastUsed = TimeUtils.now(), ), + permissions = mutableListOf(), ) - } - } else if (intentData.type == SignerType.SIGN_MESSAGE) { - if (intentData.rememberType.value != RememberType.NEVER && intentData.checked.value) { - AmberUtils.acceptOrRejectPermission( - application, - localKey, - intentData.type, - null, - true, - intentData.rememberType.value, - thisAccount, - ) + + if (!application.application.closeApplication) { + closeApp = false } - database.dao().insertApplicationWithPermissions(application) - historyDatabase.dao().addHistory( - HistoryEntity( - 0, - localKey, - intentData.type.toString(), - null, - TimeUtils.now(), - intentData.checked.value, - ), - thisAccount.npub, - ) + if (intentData.type == SignerType.SIGN_EVENT) { + val localEvent = intentData.event!! - val signedMessage = thisAccount.signString(intentData.data) - if (intentData.checked.value) { - results.add( - Result( - null, - signature = signedMessage, - result = signedMessage, - id = intentData.id, - rejected = null, + if (intentData.rememberType.value != RememberType.NEVER && intentData.checked.value) { + AmberUtils.acceptOrRejectPermission( + application, + localKey, + intentData.type, + localEvent.kind, + true, + intentData.rememberType.value, + thisAccount, + ) + } + + database.dao().insertApplicationWithPermissions(application) + + historyDatabase.dao().addHistory( + HistoryEntity( + 0, + localKey, + intentData.type.toString(), + localEvent.kind, + TimeUtils.now(), + intentData.checked.value, ), + thisAccount.npub, ) - } - } else { - if (intentData.rememberType.value != RememberType.NEVER && intentData.checked.value) { - AmberUtils.acceptOrRejectPermission( - application, - localKey, - intentData.type, - null, - true, - intentData.rememberType.value, - thisAccount, - ) - } - database.dao().insertApplicationWithPermissions(application) - - historyDatabase.dao().addHistory( - HistoryEntity( - 0, - localKey, - intentData.type.toString(), - null, - TimeUtils.now(), - intentData.checked.value, - ), - thisAccount.npub, - ) - - val signature = intentData.encryptedData?.result ?: continue - if (intentData.checked.value) { - results.add( - Result( + if (intentData.checked.value) { + results.add( + Result( + null, + signature = if (localEvent is LnZapRequestEvent && + localEvent.tags.any { tag -> + tag.any { t -> t == "anon" } + } + ) { + localEvent.toJson() + } else { + localEvent.sig + }, + result = if (localEvent is LnZapRequestEvent && + localEvent.tags.any { tag -> + tag.any { t -> t == "anon" } + } + ) { + localEvent.toJson() + } else { + localEvent.sig + }, + id = intentData.id, + rejected = null, + ), + ) + } + } else if (intentData.type == SignerType.SIGN_MESSAGE) { + if (intentData.rememberType.value != RememberType.NEVER && intentData.checked.value) { + AmberUtils.acceptOrRejectPermission( + application, + localKey, + intentData.type, null, - signature = signature, - result = signature, - id = intentData.id, - rejected = null, + true, + intentData.rememberType.value, + thisAccount, + ) + } + + database.dao().insertApplicationWithPermissions(application) + historyDatabase.dao().addHistory( + HistoryEntity( + 0, + localKey, + intentData.type.toString(), + null, + TimeUtils.now(), + intentData.checked.value, ), + thisAccount.npub, ) + + val signedMessage = thisAccount.signString(intentData.data) + if (intentData.checked.value) { + results.add( + Result( + null, + signature = signedMessage, + result = signedMessage, + id = intentData.id, + rejected = null, + ), + ) + } + } else { + if (intentData.rememberType.value != RememberType.NEVER && intentData.checked.value) { + AmberUtils.acceptOrRejectPermission( + application, + localKey, + intentData.type, + null, + true, + intentData.rememberType.value, + thisAccount, + ) + } + + database.dao().insertApplicationWithPermissions(application) + + historyDatabase.dao().addHistory( + HistoryEntity( + 0, + localKey, + intentData.type.toString(), + null, + TimeUtils.now(), + intentData.checked.value, + ), + thisAccount.npub, + ) + + val signature = intentData.encryptedData?.result ?: continue + if (intentData.checked.value) { + results.add( + Result( + null, + signature = signature, + result = signature, + id = intentData.id, + rejected = null, + ), + ) + } } } - } - if (results.isNotEmpty()) { - sendResultIntent(results) - } + if (results.isNotEmpty()) { + sendResultIntent(results) + } - finishActivity(closeApp) - } finally { - onLoading(false) - } - } - }, - ) - - AmberButton( - Modifier.padding(top = 20.dp, bottom = 40.dp), - colors = ButtonDefaults.buttonColors().copy( - containerColor = orange, - ), - onClick = { - Amber.instance.applicationIOScope.launch { - var closeApp = true - onRemoveIntentData(intents, IntentResultType.REMOVE) - for (intentData in intents) { - val thisAccount = - if (intentData.currentAccount.isNotBlank()) { - LocalPreferences.loadFromEncryptedStorage( - context, - intentData.currentAccount, - ) - } else { - accountParam - } ?: continue - - val localKey = packageName ?: continue - val database = Amber.instance.getDatabase(thisAccount.npub) - val application = - database - .dao() - .getByKey(localKey) ?: ApplicationWithPermissions( - application = ApplicationEntity( - localKey, - "", - listOf(), - "", - "", - "", - thisAccount.hexKey, - true, - "", - false, - thisAccount.signPolicy, - true, - 0L, - lastUsed = TimeUtils.now(), - ), - permissions = mutableListOf(), - ) - - if (intentData.rememberType.value != RememberType.NEVER && intentData.checked.value) { - AmberUtils.acceptOrRejectPermission( - application, - localKey, - intentData.type, - null, - false, - intentData.rememberType.value, - thisAccount, - ) - } - - if (!application.application.closeApplication) { - closeApp = false + finishActivity(closeApp) + } finally { + onLoading(false) } } - sendRejectIntent( - results = intents.map { - Result( - null, - signature = null, - result = null, - id = it.id, - rejected = true, - ) - }.toMutableList(), - ) - finishActivity(closeApp) - } - }, - text = stringResource(R.string.discard_all_requests), - ) + }, + ) + } } } @@ -571,14 +601,41 @@ fun BunkerMultiEventHomeScreen( onLoading: (Boolean) -> Unit, ) { val context = LocalContext.current - val grouped = bunkerRequests.groupBy { BunkerRequestUtils.getTypeFromBunker(it.request) }.filter { it.key != SignerType.SIGN_EVENT } - val grouped2 = bunkerRequests.filter { it.request is BunkerRequestSign }.groupBy { (it.request as BunkerRequestSign).event.kind } - val acceptEventsGroup1 = grouped.map { + val groupedEventEncryptedData = bunkerRequests.filter { it.encryptedData is EventEncryptedDataKind }.groupBy { BunkerRequestUtils.getTypeFromBunker(it.request) } + val groupedTextEncryptedData = bunkerRequests.filter { it.encryptedData is ClearTextEncryptedDataKind }.groupBy { BunkerRequestUtils.getTypeFromBunker(it.request) } + val groupedTagArrayEncryptedData = bunkerRequests.filter { it.encryptedData is TagArrayEncryptedDataKind }.groupBy { BunkerRequestUtils.getTypeFromBunker(it.request) } + val groupedPrivateZapEncryptedDataKind = bunkerRequests.filter { it.encryptedData is PrivateZapEncryptedDataKind }.groupBy { BunkerRequestUtils.getTypeFromBunker(it.request) } + val groupedOthers = bunkerRequests.filter { it.encryptedData == null && it.request !is BunkerRequestSign }.groupBy { BunkerRequestUtils.getTypeFromBunker(it.request) } + val groupedEvents = bunkerRequests.filter { it.request is BunkerRequestSign }.groupBy { (it.request as BunkerRequestSign).event.kind } + val acceptedGroupedEventEncryptedData = groupedEventEncryptedData.map { remember { mutableStateOf(true) } } - val acceptEventsGroup2 = grouped2.map { + val acceptedGroupedTextEncryptedData = groupedTextEncryptedData.map { + remember { + mutableStateOf(true) + } + } + val acceptedGroupedTagArrayEncryptedData = groupedTagArrayEncryptedData.map { + remember { + mutableStateOf(true) + } + } + + val acceptedGroupedPrivateZapEncryptedDataKind = groupedPrivateZapEncryptedDataKind.map { + remember { + mutableStateOf(true) + } + } + + val acceptEventsGroupOthers = groupedOthers.map { + remember { + mutableStateOf(true) + } + } + + val acceptEventsGroup2 = groupedEvents.map { remember { mutableStateOf(true) } @@ -586,6 +643,7 @@ fun BunkerMultiEventHomeScreen( var localAccount by remember { mutableStateOf("") } val key = bunkerRequests.first().localKey var rememberType by remember { mutableStateOf(RememberType.NEVER) } + var appName by remember { mutableStateOf(ApplicationNameCache.names["$localAccount-$key"] ?: key.toShortenHex()) } LaunchedEffect(Unit) { launch(Dispatchers.IO) { @@ -593,11 +651,21 @@ fun BunkerMultiEventHomeScreen( context, bunkerRequests.first().currentAccount, )?.npub?.toShortenHex() ?: "" + + 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 + } + } } } - val appName = ApplicationNameCache.names["$localAccount-$key"] ?: key.toShortenHex() - Column( modifier, ) { @@ -608,63 +676,120 @@ fun BunkerMultiEventHomeScreen( .padding(bottom = 20.dp), ) - grouped.toList().forEachIndexed { index, it -> - BunkerPermissionCard( - context = context, - acceptEventsGroup = acceptEventsGroup1, - index = index, - item = it, - onDetailsClick = { - MultiEventScreenIntents.bunkerRequests = it - MultiEventScreenIntents.appName = appName - navController.navigate(Route.SeeDetails.route) - }, - ) - } - grouped2.toList().forEachIndexed { index, it -> - BunkerPermissionCard( - context = context, - acceptEventsGroup = acceptEventsGroup2, - index = index, - item = it, - onDetailsClick = { - MultiEventScreenIntents.bunkerRequests = it - MultiEventScreenIntents.appName = appName - navController.navigate(Route.SeeDetails.route) - }, - ) + Column( + Modifier + .weight(1f) + .verticalScroll(rememberScrollState()), + ) { + groupedEventEncryptedData.toList().forEachIndexed { index, it -> + BunkerPermissionCard( + context = context, + acceptEventsGroup = acceptedGroupedEventEncryptedData, + index = index, + item = it, + onDetailsClick = { + MultiEventScreenIntents.bunkerRequests = it + MultiEventScreenIntents.appName = appName + navController.navigate(Route.SeeDetails.route) + }, + ) + } + groupedTextEncryptedData.toList().forEachIndexed { index, it -> + BunkerPermissionCard( + context = context, + acceptEventsGroup = acceptedGroupedTextEncryptedData, + index = index, + item = it, + onDetailsClick = { + MultiEventScreenIntents.bunkerRequests = it + MultiEventScreenIntents.appName = appName + navController.navigate(Route.SeeDetails.route) + }, + ) + } + groupedTagArrayEncryptedData.toList().forEachIndexed { index, it -> + BunkerPermissionCard( + context = context, + acceptEventsGroup = acceptedGroupedTagArrayEncryptedData, + index = index, + item = it, + onDetailsClick = { + MultiEventScreenIntents.bunkerRequests = it + MultiEventScreenIntents.appName = appName + navController.navigate(Route.SeeDetails.route) + }, + ) + } + groupedPrivateZapEncryptedDataKind.toList().forEachIndexed { index, it -> + BunkerPermissionCard( + context = context, + acceptEventsGroup = acceptedGroupedPrivateZapEncryptedDataKind, + index = index, + item = it, + onDetailsClick = { + MultiEventScreenIntents.bunkerRequests = it + MultiEventScreenIntents.appName = appName + navController.navigate(Route.SeeDetails.route) + }, + ) + } + groupedOthers.toList().forEachIndexed { index, it -> + BunkerPermissionCard( + context = context, + acceptEventsGroup = acceptEventsGroupOthers, + index = index, + item = it, + onDetailsClick = { + MultiEventScreenIntents.bunkerRequests = it + MultiEventScreenIntents.appName = appName + navController.navigate(Route.SeeDetails.route) + }, + ) + } + groupedEvents.toList().forEachIndexed { index, it -> + BunkerPermissionCard( + context = context, + acceptEventsGroup = acceptEventsGroup2, + index = index, + item = it, + onDetailsClick = { + MultiEventScreenIntents.bunkerRequests = it + MultiEventScreenIntents.appName = appName + navController.navigate(Route.SeeDetails.route) + }, + ) + } } + RememberMyChoice( + alwaysShow = true, + shouldRunAcceptOrReject = null, + onAccept = {}, + onReject = {}, + onChanged = { + rememberType = it + bunkerRequests.forEach { bunkerRequest -> + bunkerRequest.rememberType.value = rememberType + } + }, + packageName = packageName, + ) + Row( - Modifier.padding(vertical = 40.dp), + Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - RememberMyChoice( - alwaysShow = true, - shouldRunAcceptOrReject = null, - onAccept = {}, - onReject = {}, - onChanged = { - rememberType = it - bunkerRequests.forEach { bunkerRequest -> - bunkerRequest.rememberType.value = rememberType - } - }, - packageName = packageName, - ) - } - - AmberButton( - Modifier.padding(bottom = 40.dp), - text = stringResource(R.string.approve_selected), - onClick = { - onLoading(true) - Amber.instance.applicationIOScope.launch(Dispatchers.IO) { - try { - reconnectToRelays() - val closeApp = bunkerRequests.any { it.closeApplication } + AmberButton( + Modifier.weight(1f), + colors = ButtonDefaults.buttonColors().copy( + containerColor = orange, + ), + onClick = { + Amber.instance.applicationIOScope.launch { + var closeApp = true BunkerRequestUtils.clearRequests() - EventNotificationConsumer(context).notificationManager().cancelAll() - finishActivity(closeApp) for (request in bunkerRequests) { val thisAccount = if (request.currentAccount.isNotBlank()) { @@ -678,17 +803,15 @@ fun BunkerMultiEventHomeScreen( val localKey = request.localKey val database = Amber.instance.getDatabase(thisAccount.npub) - val historyDatabase = Amber.instance.getHistoryDatabase(thisAccount.npub) - val savedApplication = database.dao().getByKey(localKey) - val secret = if (request.request is BunkerRequestConnect) { request.request.secret ?: "" } else { "" } - val application = - savedApplication ?: ApplicationWithPermissions( + database + .dao() + .getByKey(localKey) ?: ApplicationWithPermissions( application = ApplicationEntity( localKey, "", @@ -708,125 +831,160 @@ fun BunkerMultiEventHomeScreen( permissions = mutableListOf(), ) - if (request.request is BunkerRequestSign) { - val localEvent = request.signedEvent!! - - if (request.rememberType.value != RememberType.NEVER && request.checked.value) { - AmberUtils.acceptOrRejectPermission( - application = application, - key = localKey, - signerType = SignerType.SIGN_EVENT, - kind = localEvent.kind, - value = true, - rememberType = request.rememberType.value, - account = thisAccount, - ) - } - - database.dao().insertApplicationWithPermissions(application) - - historyDatabase.dao().addHistory( - entity = HistoryEntity( - id = 0, - pkKey = localKey, - type = SignerType.SIGN_EVENT.toString(), - kind = localEvent.kind, - time = TimeUtils.now(), - accepted = request.checked.value, - ), - thisAccount.npub, + if (request.rememberType.value != RememberType.NEVER && request.checked.value) { + AmberUtils.acceptOrRejectPermission( + application, + localKey, + BunkerRequestUtils.getTypeFromBunker(request.request), + null, + false, + request.rememberType.value, + thisAccount, ) + } - val localBunkerRequest = request.copy() - BunkerRequestUtils.remove(request.request.id) + if (!application.application.closeApplication) { + closeApp = false + } + } - if (request.checked.value) { - BunkerRequestUtils.sendBunkerResponse( - context, - thisAccount, - request, - BunkerResponse(request.request.id, localEvent.toJson(), null), - application.application.relays, - onLoading = {}, - onDone = { - if (!it) { - BunkerRequestUtils.addRequest(localBunkerRequest) - } - }, - ) + EventNotificationConsumer(context).notificationManager().cancelAll() + finishActivity(closeApp) + } + }, + 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 database = Amber.instance.getDatabase(thisAccount.npub) + val historyDatabase = Amber.instance.getHistoryDatabase(thisAccount.npub) + val savedApplication = database.dao().getByKey(localKey) + + val secret = if (request.request is BunkerRequestConnect) { + request.request.secret ?: "" } else { - AmberUtils.sendBunkerError( - account = thisAccount, - bunkerRequest = request, - relays = application.application.relays, - context = context, - closeApplication = application.application.closeApplication, - onLoading = {}, - ) - } - } else if (request.request.method == "sign_message") { - if (request.rememberType.value != RememberType.NEVER && request.checked.value) { - AmberUtils.acceptOrRejectPermission( - application, - localKey, - SignerType.SIGN_MESSAGE, - null, - true, - request.rememberType.value, - thisAccount, - ) + "" } - database.dao().insertApplicationWithPermissions(application) - historyDatabase.dao().addHistory( - HistoryEntity( - 0, - localKey, - SignerType.SIGN_MESSAGE.toString(), - null, - TimeUtils.now(), - request.checked.value, - ), - thisAccount.npub, - ) - - val signedMessage = thisAccount.signString(request.request.params.first()) - val localBunkerRequest = request.copy() - BunkerRequestUtils.remove(localBunkerRequest.request.id) - - if (request.checked.value) { - BunkerRequestUtils.sendBunkerResponse( - context, - thisAccount, - request, - BunkerResponse(request.request.id, signedMessage, null), - application.application.relays, - onLoading = {}, - onDone = { - if (!it) { - BunkerRequestUtils.addRequest(localBunkerRequest) - } - }, + val application = + savedApplication ?: ApplicationWithPermissions( + application = ApplicationEntity( + localKey, + "", + listOf(), + "", + "", + "", + thisAccount.hexKey, + true, + secret, + secret.isNotBlank(), + thisAccount.signPolicy, + request.closeApplication, + 0L, + lastUsed = TimeUtils.now(), + ), + permissions = mutableListOf(), ) - } 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) { + + if (request.request is BunkerRequestSign) { + val localEvent = request.signedEvent!! + + if (request.rememberType.value != RememberType.NEVER && request.checked.value) { + AmberUtils.acceptOrRejectPermission( + application = application, + key = localKey, + signerType = SignerType.SIGN_EVENT, + kind = localEvent.kind, + value = true, + rememberType = request.rememberType.value, + account = thisAccount, + ) + } + database.dao().insertApplicationWithPermissions(application) + historyDatabase.dao().addHistory( + entity = HistoryEntity( + id = 0, + pkKey = localKey, + type = SignerType.SIGN_EVENT.toString(), + kind = localEvent.kind, + time = TimeUtils.now(), + accepted = request.checked.value, + ), + thisAccount.npub, + ) + + val localBunkerRequest = request.copy() + BunkerRequestUtils.remove(request.request.id) + + if (request.checked.value) { + BunkerRequestUtils.sendBunkerResponse( + context, + thisAccount, + request, + BunkerResponse(request.request.id, localEvent.toJson(), null), + application.application.relays, + onLoading = {}, + onDone = { + if (!it) { + BunkerRequestUtils.addRequest(localBunkerRequest) + } + }, + ) + } else { + AmberUtils.sendBunkerError( + account = thisAccount, + bunkerRequest = request, + relays = application.application.relays, + context = context, + closeApplication = application.application.closeApplication, + onLoading = {}, + ) + } + } else if (request.request.method == "sign_message") { + if (request.rememberType.value != RememberType.NEVER && request.checked.value) { + AmberUtils.acceptOrRejectPermission( + application, + localKey, + SignerType.SIGN_MESSAGE, + null, + true, + request.rememberType.value, + thisAccount, + ) + } + + database.dao().insertApplicationWithPermissions(application) historyDatabase.dao().addHistory( HistoryEntity( 0, localKey, - SignerType.CONNECT.toString(), + SignerType.SIGN_MESSAGE.toString(), null, TimeUtils.now(), request.checked.value, @@ -834,6 +992,106 @@ fun BunkerMultiEventHomeScreen( thisAccount.npub, ) + val signedMessage = thisAccount.signString(request.request.params.first()) + val localBunkerRequest = request.copy() + BunkerRequestUtils.remove(localBunkerRequest.request.id) + + if (request.checked.value) { + BunkerRequestUtils.sendBunkerResponse( + context, + thisAccount, + request, + BunkerResponse(request.request.id, signedMessage, null), + application.application.relays, + onLoading = {}, + onDone = { + if (!it) { + BunkerRequestUtils.addRequest(localBunkerRequest) + } + }, + ) + } 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) { + database.dao().insertApplicationWithPermissions(application) + + historyDatabase.dao().addHistory( + HistoryEntity( + 0, + localKey, + SignerType.CONNECT.toString(), + null, + TimeUtils.now(), + request.checked.value, + ), + thisAccount.npub, + ) + + val localBunkerRequest = request.copy() + BunkerRequestUtils.remove(request.request.id) + if (request.checked.value) { + BunkerRequestUtils.sendBunkerResponse( + context, + thisAccount, + request, + BunkerResponse(request.request.id, "", null), + application.application.relays, + onLoading = {}, + onDone = { + if (!it) { + BunkerRequestUtils.addRequest(localBunkerRequest) + } + }, + ) + } else { + AmberUtils.sendBunkerError( + account = thisAccount, + bunkerRequest = request, + relays = application.application.relays, + context = context, + closeApplication = application.application.closeApplication, + onLoading = {}, + ) + } + } + } else { + val type = BunkerRequestUtils.getTypeFromBunker(request.request) + if (request.rememberType.value != RememberType.NEVER && request.checked.value) { + AmberUtils.acceptOrRejectPermission( + application, + localKey, + type, + null, + true, + request.rememberType.value, + thisAccount, + ) + } + + database.dao().insertApplicationWithPermissions(application) + + historyDatabase.dao().addHistory( + HistoryEntity( + 0, + localKey, + type.toString(), + null, + TimeUtils.now(), + request.checked.value, + ), + thisAccount.npub, + ) + + val signature = request.encryptedData?.result ?: continue val localBunkerRequest = request.copy() BunkerRequestUtils.remove(request.request.id) if (request.checked.value) { @@ -841,7 +1099,7 @@ fun BunkerMultiEventHomeScreen( context, thisAccount, request, - BunkerResponse(request.request.id, "", null), + BunkerResponse(request.request.id, signature, null), application.application.relays, onLoading = {}, onDone = { @@ -861,143 +1119,14 @@ fun BunkerMultiEventHomeScreen( ) } } - } else { - val type = BunkerRequestUtils.getTypeFromBunker(request.request) - if (request.rememberType.value != RememberType.NEVER && request.checked.value) { - AmberUtils.acceptOrRejectPermission( - application, - localKey, - type, - null, - true, - request.rememberType.value, - thisAccount, - ) - } - - database.dao().insertApplicationWithPermissions(application) - - historyDatabase.dao().addHistory( - HistoryEntity( - 0, - localKey, - type.toString(), - null, - TimeUtils.now(), - request.checked.value, - ), - thisAccount.npub, - ) - - val signature = request.encryptedData?.result ?: continue - val localBunkerRequest = request.copy() - BunkerRequestUtils.remove(request.request.id) - if (request.checked.value) { - BunkerRequestUtils.sendBunkerResponse( - context, - thisAccount, - request, - BunkerResponse(request.request.id, signature, null), - application.application.relays, - onLoading = {}, - onDone = { - if (!it) { - BunkerRequestUtils.addRequest(localBunkerRequest) - } - }, - ) - } else { - AmberUtils.sendBunkerError( - account = thisAccount, - bunkerRequest = request, - relays = application.application.relays, - context = context, - closeApplication = application.application.closeApplication, - onLoading = {}, - ) - } } - } - } finally { - onLoading(false) - } - } - }, - ) - - AmberButton( - Modifier.padding(top = 20.dp, bottom = 40.dp), - colors = ButtonDefaults.buttonColors().copy( - containerColor = orange, - ), - onClick = { - Amber.instance.applicationIOScope.launch { - var closeApp = true - BunkerRequestUtils.clearRequests() - for (request in bunkerRequests) { - val thisAccount = - if (request.currentAccount.isNotBlank()) { - LocalPreferences.loadFromEncryptedStorage( - context, - request.currentAccount, - ) - } else { - accountParam - } ?: continue - - val localKey = request.localKey - val database = Amber.instance.getDatabase(thisAccount.npub) - val secret = if (request.request is BunkerRequestConnect) { - request.request.secret ?: "" - } else { - "" - } - val application = - database - .dao() - .getByKey(localKey) ?: ApplicationWithPermissions( - application = ApplicationEntity( - localKey, - "", - listOf(), - "", - "", - "", - thisAccount.hexKey, - true, - secret, - secret.isNotBlank(), - thisAccount.signPolicy, - request.closeApplication, - 0L, - lastUsed = TimeUtils.now(), - ), - permissions = mutableListOf(), - ) - - if (request.rememberType.value != RememberType.NEVER && request.checked.value) { - AmberUtils.acceptOrRejectPermission( - application, - localKey, - BunkerRequestUtils.getTypeFromBunker(request.request), - null, - false, - request.rememberType.value, - thisAccount, - ) - } - - if (!application.application.closeApplication) { - closeApp = false + } finally { + onLoading(false) } } - - EventNotificationConsumer(context).notificationManager().cancelAll() - finishActivity(closeApp) - } - }, - text = stringResource(R.string.discard_all_requests), - ) + }, + ) + } } } @@ -1106,6 +1235,10 @@ fun PermissionCard( stringResource(R.string.read_this_list_of_tags_from_encrypted_content, first.toString().split("_").first()) } + is PrivateZapEncryptedDataKind -> { + stringResource(R.string.decrypt_zap_event).capitalize(Locale.current) + } + else -> stringResource(R.string.read_this_text_from_encrypted_content, first.toString().split("_").first()) } } else { @@ -1216,7 +1349,42 @@ fun BunkerPermissionCard( val message = if (first == SignerType.CONNECT) { stringResource(R.string.connect) } else { - permission.toLocalizedString(context) + val encryptedData = item.second.first().encryptedData + if (first.toString().contains("ENCRYPT")) { + when (encryptedData) { + is EventEncryptedDataKind -> { + val permission = Permission("sign_event", encryptedData.event.kind) + stringResource(R.string.encrypt_with, permission.toLocalizedString(context), first.toString().split("_").first()) + } + + is TagArrayEncryptedDataKind -> { + stringResource(R.string.encrypt_this_list_of_tags_with, first.toString().split("_").first()) + } + + else -> stringResource(R.string.encrypt_this_text_with, first.toString().split("_").first()) + } + } else { + if (first.toString().contains("DECRYPT")) { + when (encryptedData) { + is EventEncryptedDataKind -> { + val permission = Permission("sign_event", encryptedData.event.kind) + stringResource(R.string.read_from_encrypted_content, permission.toLocalizedString(context), first.toString().split("_").first()) + } + + is TagArrayEncryptedDataKind -> { + stringResource(R.string.read_this_list_of_tags_from_encrypted_content, first.toString().split("_").first()) + } + + is PrivateZapEncryptedDataKind -> { + stringResource(R.string.decrypt_zap_event).capitalize(Locale.current) + } + + else -> stringResource(R.string.read_this_text_from_encrypted_content, first.toString().split("_").first()) + } + } else { + permission.toLocalizedString(context) + } + } } Text( diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt index a7192fb7..3eb13795 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/RememberMyChoice.kt @@ -1,17 +1,61 @@ package com.greenart7c3.nostrsigner.ui.components +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.ui.RememberType -import com.greenart7c3.nostrsigner.ui.SettingsRow -import kotlinx.collections.immutable.persistentListOf + +@Composable +fun LabeledBorderBox( + label: String, + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + Box(modifier = modifier) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp) + .border( + width = 1.dp, + color = Color.Gray, + shape = RoundedCornerShape(8.dp), + ) + .padding(4.dp), + ) { + content() + } + + Text( + text = label, + modifier = Modifier + .padding(start = 16.dp) + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = 8.dp), + fontSize = 12.sp, + fontWeight = FontWeight.Light, + ) + } +} @Composable fun RememberMyChoice( @@ -22,14 +66,6 @@ fun RememberMyChoice( onReject: (RememberType) -> Unit, onChanged: (RememberType) -> Unit, ) { - val items = - persistentListOf( - TitleExplainer(stringResource(RememberType.NEVER.resourceId)), - TitleExplainer(stringResource(RememberType.ONE_MINUTE.resourceId)), - TitleExplainer(stringResource(RememberType.FIVE_MINUTES.resourceId)), - TitleExplainer(stringResource(RememberType.TEN_MINUTES.resourceId)), - TitleExplainer(stringResource(RememberType.ALWAYS.resourceId)), - ) var index by remember { mutableIntStateOf(0) } @@ -43,16 +79,60 @@ fun RememberMyChoice( } } if (packageName != null || alwaysShow) { - SettingsRow( - R.string.automatically_sign_this_for, - null, - items, - index, - maxLines = Int.MAX_VALUE, - overflow = TextOverflow.Clip, + LabeledBorderBox( + label = stringResource(R.string.automatically_sign_this_for), ) { - index = it - onChanged(RememberType.entries[it]) + AmberToggles( + selectedIndex = index, + count = 5, + content = { + ToggleOption( + modifier = Modifier.width(55.dp), + text = stringResource(RememberType.NEVER.resourceId), + isSelected = RememberType.NEVER == RememberType.entries[index], + onClick = { + index = RememberType.NEVER.screenCode + onChanged(RememberType.NEVER) + }, + ) + ToggleOption( + modifier = Modifier.width(55.dp), + text = stringResource(R.string.one_minute_short), + isSelected = RememberType.ONE_MINUTE == RememberType.entries[index], + onClick = { + index = RememberType.ONE_MINUTE.screenCode + onChanged(RememberType.ONE_MINUTE) + }, + ) + ToggleOption( + modifier = Modifier.width(55.dp), + text = stringResource(R.string.five_minutes_short), + isSelected = RememberType.FIVE_MINUTES == RememberType.entries[index], + onClick = { + index = RememberType.FIVE_MINUTES.screenCode + onChanged(RememberType.FIVE_MINUTES) + }, + ) + ToggleOption( + modifier = Modifier.width(55.dp), + text = stringResource(R.string.ten_minutes_short), + isSelected = RememberType.TEN_MINUTES == RememberType.entries[index], + onClick = { + index = RememberType.TEN_MINUTES.screenCode + onChanged(RememberType.TEN_MINUTES) + }, + ) + ToggleOption( + modifier = Modifier.width(55.dp), + text = stringResource(RememberType.ALWAYS.resourceId), + isSelected = RememberType.ALWAYS == RememberType.entries[index], + onClick = { + index = RememberType.ALWAYS.screenCode + onChanged(RememberType.entries[RememberType.ALWAYS.screenCode]) + }, + ) + }, + ) } } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SignMessage.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SignMessage.kt index dd611658..b736cedb 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SignMessage.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/SignMessage.kt @@ -1,11 +1,8 @@ package com.greenart7c3.nostrsigner.ui.components -import android.content.ClipData -import android.widget.Toast import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Card @@ -14,46 +11,27 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ClipEntry -import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.greenart7c3.nostrsigner.R -import com.greenart7c3.nostrsigner.models.Account -import com.greenart7c3.nostrsigner.models.SignerType import com.greenart7c3.nostrsigner.ui.RememberType -import kotlinx.coroutines.launch @Composable fun SignMessage( - account: Account, modifier: Modifier, content: String, shouldRunOnAccept: Boolean?, packageName: String?, - applicationName: String?, - appName: String, - type: SignerType, onAccept: (RememberType) -> Unit, onReject: (RememberType) -> Unit, ) { - var showMore by androidx.compose.runtime.remember { - mutableStateOf(false) - } - val clipboardManager = LocalClipboard.current - val coroutineScope = rememberCoroutineScope() - val context = LocalContext.current var rememberType by remember { mutableStateOf(RememberType.NEVER) } @@ -61,28 +39,13 @@ fun SignMessage( Column( modifier, ) { - ProfilePicture(account) + LocalAppIcon(packageName) val message = stringResource(R.string.sign_message) - packageName?.let { - Text( - modifier = Modifier - .fillMaxWidth(), - text = it, - fontWeight = FontWeight.Bold, - fontSize = 18.sp, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.size(4.dp)) - } - Text( buildAnnotatedString { - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append(applicationName ?: appName) - } - append(" requests $message") + append(stringResource(R.string.requests_message, message)) }, fontSize = 18.sp, ) @@ -100,8 +63,6 @@ fun SignMessage( Text( content, - maxLines = 1, - overflow = TextOverflow.Ellipsis, modifier = Modifier .fillMaxWidth() .padding(top = 8.dp), @@ -109,39 +70,7 @@ fun SignMessage( } } - RawJsonButton( - onCLick = { - showMore = !showMore - }, - if (!showMore) stringResource(R.string.show_details) else stringResource(R.string.hide_details), - ) - if (showMore) { - RawJson( - content, - "", - Modifier.height(200.dp), - stringResource(R.string.content), - type, - ) { - coroutineScope.launch { - clipboardManager.setClipEntry( - ClipEntry( - ClipData.newPlainText("", content), - ), - ) - } - - coroutineScope.launch { - Toast.makeText( - context, - context.getString(R.string.data_copied_to_the_clipboard), - Toast.LENGTH_SHORT, - ).show() - } - } - } else { - Spacer(modifier = Modifier.weight(1f)) - } + Spacer(Modifier.weight(1f)) RememberMyChoice( shouldRunOnAccept, @@ -166,21 +95,13 @@ fun SignMessage( @Composable fun BunkerSignMessage( - account: Account, modifier: Modifier, content: String, shouldRunOnAccept: Boolean?, appName: String, - type: SignerType, onAccept: (RememberType) -> Unit, onReject: (RememberType) -> Unit, ) { - var showMore by androidx.compose.runtime.remember { - mutableStateOf(false) - } - val clipboardManager = LocalClipboard.current - val coroutineScope = rememberCoroutineScope() - val context = LocalContext.current var rememberType by remember { mutableStateOf(RememberType.NEVER) } @@ -188,8 +109,6 @@ fun BunkerSignMessage( Column( modifier, ) { - ProfilePicture(account) - val message = stringResource(R.string.sign_message) Text( @@ -209,14 +128,12 @@ fun BunkerSignMessage( ) { Column(Modifier.padding(6.dp)) { Text( - "Event content", + stringResource(R.string.content), fontWeight = FontWeight.Bold, ) Text( content, - maxLines = 1, - overflow = TextOverflow.Ellipsis, modifier = Modifier .fillMaxWidth() .padding(top = 8.dp), @@ -224,39 +141,7 @@ fun BunkerSignMessage( } } - RawJsonButton( - onCLick = { - showMore = !showMore - }, - if (!showMore) stringResource(R.string.show_details) else stringResource(R.string.hide_details), - ) - if (showMore) { - RawJson( - content, - "", - Modifier.height(200.dp), - stringResource(R.string.content), - type, - ) { - coroutineScope.launch { - clipboardManager.setClipEntry( - ClipEntry( - ClipData.newPlainText("", content), - ), - ) - } - - coroutineScope.launch { - Toast.makeText( - context, - context.getString(R.string.data_copied_to_the_clipboard), - Toast.LENGTH_SHORT, - ).show() - } - } - } else { - Spacer(modifier = Modifier.weight(1f)) - } + Spacer(Modifier.weight(1f)) RememberMyChoice( shouldRunOnAccept, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/TagsSection.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/TagsSection.kt new file mode 100644 index 00000000..590866f5 --- /dev/null +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/TagsSection.kt @@ -0,0 +1,93 @@ +package com.greenart7c3.nostrsigner.ui.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ContentCopy +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.greenart7c3.nostrsigner.R + +@Composable +fun TagsSection( + label: String, + tags: Array>, + onCopy: (String) -> Unit, +) { + val tagsAsStringForCopy = remember(tags) { + tags.toString() + } + + val tagsToShow = tags.take(10) + val moreCount = tags.size - 10 + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Top, + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = label, + style = MaterialTheme.typography.bodyLarge.copy(fontSize = 16.sp, lineHeight = 20.sp), + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, + ) + if (tags.isNotEmpty()) { + Column( + modifier = Modifier.padding(top = 12.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + tagsToShow.forEach { tag -> + val formattedTag = tag.joinToString(separator = ", ") { + "\"${it}\"" + } + Text( + text = formattedTag, + style = MaterialTheme.typography.bodyMedium.copy( + lineHeight = 24.sp, + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Bold, + ) + } + + if (moreCount > 0) { + Text( + text = stringResource(id = R.string.more, moreCount), + style = MaterialTheme.typography.bodyMedium.copy( + lineHeight = 24.sp, + ), + fontWeight = FontWeight.Bold, + ) + } + } + } + } + Icon( + modifier = Modifier + .size(16.dp) + .clickable { onCopy(tagsAsStringForCopy) }, + imageVector = Icons.Default.ContentCopy, + contentDescription = stringResource(id = R.string.copy_to_clipboard), + ) + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 31dbcd29..b5da2f19 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -260,6 +260,9 @@ One minute Five minutes Ten Minutes + 1m + 5m + 10m Add user to group Remove user from group Edit group metadata @@ -450,6 +453,7 @@ %1$s of %2$s events See details %1$s is requiring some permissions, please review them. + Is requiring some permissions, please review them. Always approve/reject this permission %1$s is requiring to sign these events related to %2$s permission. Relay does not support retrieving Bunker events. Would you like to add it anyway? @@ -611,4 +615,11 @@ Good wiki relays Relay status notification Relay status + Kind + Pubkey + Date + Tags + Discard selected + Approve selected + Requests %1$s