mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-06 03:18:23 +00:00
Refactor UI components and enhance multi-request handling
- Redesign `RememberMyChoice` using a new `LabeledBorderBox` and `AmberToggles` for a more compact selection. - Introduce `LocalAppIcon` to display application icons and names consistently across screens. - Implement `EventDetailModal`, `EventSection`, and `TagsSection` to provide detailed, scrollable event information. - Refactor `MultiEventHomeScreen` to support vertical scrolling and grouped request approval/discard actions. - Update `EventData`, `SignMessage`, and `EncryptDecryptData` to use the new UI patterns and improve permission messaging. - Add short-form string resources for time intervals (1m, 5m, 10m) and new L10n strings for event details. - Optimize button styles and layouts in `AmberButton` and `AcceptRejectButtons`. - Support `PrivateZapEncryptedDataKind` in decryption flows.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AmberBunkerRequest>,
|
||||
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 = {
|
||||
|
||||
+18
-46
@@ -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<Permission>?, 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)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
-9
@@ -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())
|
||||
|
||||
+192
-167
@@ -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<String>,
|
||||
)
|
||||
|
||||
@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<String>,
|
||||
)
|
||||
|
||||
@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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
-12
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
+790
-622
File diff suppressed because it is too large
Load Diff
@@ -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])
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<Array<String>>,
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -260,6 +260,9 @@
|
||||
<string name="one_minute">One minute</string>
|
||||
<string name="five_minutes">Five minutes</string>
|
||||
<string name="ten_minutes">Ten Minutes</string>
|
||||
<string name="one_minute_short">1m</string>
|
||||
<string name="five_minutes_short">5m</string>
|
||||
<string name="ten_minutes_short">10m</string>
|
||||
<string name="event_kind_9000">Add user to group</string>
|
||||
<string name="event_kind_9001">Remove user from group</string>
|
||||
<string name="event_kind_9002">Edit group metadata</string>
|
||||
@@ -450,6 +453,7 @@
|
||||
<string name="of_events">%1$s of %2$s events</string>
|
||||
<string name="see_details">See details</string>
|
||||
<string name="is_requiring_some_permissions_please_review_them">%1$s is requiring some permissions, please review them.</string>
|
||||
<string name="is_requiring_some_permissions_please_review_them2">Is requiring some permissions, please review them.</string>
|
||||
<string name="always_approve_this_permission">Always approve/reject this permission</string>
|
||||
<string name="is_requiring_to_sign_these_events_related_to_permission">%1$s is requiring to sign these events related to %2$s permission.</string>
|
||||
<string name="relay_filter_failed">Relay does not support retrieving Bunker events. Would you like to add it anyway?</string>
|
||||
@@ -611,4 +615,11 @@
|
||||
<string name="event_kind_10102">Good wiki relays</string>
|
||||
<string name="status_service_description">Relay status notification</string>
|
||||
<string name="status">Relay status</string>
|
||||
<string name="kind">Kind</string>
|
||||
<string name="pubkey">Pubkey</string>
|
||||
<string name="date">Date</string>
|
||||
<string name="tags">Tags</string>
|
||||
<string name="discard_all">Discard selected</string>
|
||||
<string name="approve_all">Approve selected</string>
|
||||
<string name="requests_message">Requests %1$s</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user