From eca10d5dcf5515626c0187449e7f4747ca0d4d59 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 5 Oct 2026 11:31:15 -0300 Subject: [PATCH 1/8] fix: use duotone folder icon in auth permissions Co-Authored-By: Claude Opus 5.5 --- app/src/main/res/drawable/ic_folder.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/drawable/ic_folder.xml b/app/src/main/res/drawable/ic_folder.xml index 24594f7081..800acf03ed 100644 --- a/app/src/main/res/drawable/ic_folder.xml +++ b/app/src/main/res/drawable/ic_folder.xml @@ -3,6 +3,10 @@ android:height="24dp" android:viewportWidth="256" android:viewportHeight="256"> + From 12cbf997f6e57093354e92fed84828bd8b5fa574 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 5 Oct 2026 11:31:16 -0300 Subject: [PATCH 2/8] fix: align paykit auth sheet with figma Co-Authored-By: Claude Opus 5.5 --- .../screens/profile/PubkyAuthApprovalSheet.kt | 249 ++++++++++-------- app/src/main/res/values/strings.xml | 10 +- 2 files changed, 143 insertions(+), 116 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt b/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt index 7167a32a90..328a1e5c58 100644 --- a/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt +++ b/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -15,7 +16,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -27,11 +27,10 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -51,9 +50,9 @@ import to.bitkit.ui.components.BodyMSB import to.bitkit.ui.components.BodyS import to.bitkit.ui.components.BodySSB import to.bitkit.ui.components.BottomSheetPreview +import to.bitkit.ui.components.ButtonSize import to.bitkit.ui.components.Display import to.bitkit.ui.components.FillHeight -import to.bitkit.ui.components.Headline import to.bitkit.ui.components.HorizontalSpacer import to.bitkit.ui.components.PrimaryButton import to.bitkit.ui.components.PubkyImage @@ -282,20 +281,24 @@ private fun ColumnScope.WatchOnlyConsentContent( Column( modifier = Modifier .weight(1f) - .padding(horizontal = 16.dp) .testTag("PubkyAuthWatchOnlyConsent") ) { - FillHeight(min = 26.dp) + FillHeight(min = 32.dp) Image( - painter = painterResource(R.drawable.coin_stack), + painter = painterResource(R.drawable.coin_stack_4), contentDescription = null, modifier = Modifier .size(256.dp) - .align(Alignment.CenterHorizontally), + .align(Alignment.CenterHorizontally) + .graphicsLayer { + scaleX = COIN_SCALE + scaleY = COIN_SCALE + translationY = COIN_OFFSET_Y.toPx() + }, ) - VerticalSpacer(36.dp) + VerticalSpacer(32.dp) Display( text = stringResource(R.string.profile__auth_approval_watch_only_intro_title) @@ -372,14 +375,17 @@ private fun ColumnScope.AuthorizingContent( ) { ApprovalDetails(uiState = uiState) - BodyMSB( - text = stringResource(R.string.profile__auth_approval_authorizing), - color = Colors.White32, - textAlign = androidx.compose.ui.text.style.TextAlign.Center, + Box( + contentAlignment = Alignment.Center, modifier = Modifier .fillMaxWidth() - .padding(vertical = 18.dp), - ) + .height(ButtonSize.Large.height) + ) { + BodySSB( + text = stringResource(R.string.profile__auth_approval_authorizing), + color = Colors.White32, + ) + } VerticalSpacer(16.dp) } @@ -387,64 +393,55 @@ private fun ColumnScope.AuthorizingContent( private fun ColumnScope.ApprovalDetails( uiState: PubkyAuthApprovalUiState, ) { - Column(modifier = Modifier.weight(1f).verticalScroll(rememberScrollState())) { - VerticalSpacer(26.dp) + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + ) { + VerticalSpacer(8.dp) if (uiState.createsIdentity) { BodyM(text = stringResource(R.string.pubky_auth__signup_description), color = Colors.White64) VerticalSpacer(16.dp) } if (uiState.permissions.isNotEmpty()) { - DescriptionText(serviceName = uiState.serviceName) - VerticalSpacer(8.dp) - } - if (uiState.clientId.isNotBlank()) { - BodyS( - text = stringResource(R.string.profile__auth_approval_requester, uiState.clientId), - color = Colors.White64, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) + DescriptionText(clientId = uiState.clientId, serviceName = uiState.serviceName) VerticalSpacer(32.dp) - } else { - VerticalSpacer(24.dp) - } - - if (uiState.permissions.isNotEmpty()) { PermissionsSection(permissions = uiState.permissions) } if (uiState.bitkitClaim?.includesPaykitAccess == true) { - VerticalSpacer(16.dp) + VerticalSpacer(32.dp) PaykitAccessSection() } - VerticalSpacer(32.dp) - - TrustWarning() - VerticalSpacer(16.dp) - - uiState.homeserverPublicKey?.takeIf { uiState.createsIdentity }?.let { homeserver -> - Column( - verticalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier - .fillMaxWidth() - .background(Colors.Gray6, RoundedCornerShape(16.dp)) - .padding(24.dp) - .testTag("PubkySignupHomeserver") - ) { - Text13Up(text = stringResource(R.string.pubky_auth__homeserver), color = Colors.White64) - BodyMSB(text = homeserver) - } - } ?: uiState.profile?.let { ProfileCard(it) } VerticalSpacer(16.dp) } + + TrustWarning() + VerticalSpacer(16.dp) + + uiState.homeserverPublicKey?.takeIf { uiState.createsIdentity }?.let { homeserver -> + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .fillMaxWidth() + .background(Colors.Gray6, RoundedCornerShape(16.dp)) + .padding(24.dp) + .testTag("PubkySignupHomeserver") + ) { + Text13Up(text = stringResource(R.string.pubky_auth__homeserver), color = Colors.White64) + BodyMSB(text = homeserver) + } + } ?: uiState.profile?.let { ProfileCard(it) } + VerticalSpacer(24.dp) } @Composable private fun PaykitAccessSection() { - Column(modifier = Modifier.testTag("PubkyAuthPaykitAccess")) { - BodyMSB(text = stringResource(R.string.profile__auth_approval_paykit_access_title)) - VerticalSpacer(8.dp) - BodyM(text = stringResource(R.string.profile__auth_approval_paykit_access_description), color = Colors.White64) + Section( + title = stringResource(R.string.profile__auth_approval_details), + modifier = Modifier.testTag("PubkyAuthPaykitAccess") + ) { + BodyS(text = stringResource(R.string.profile__auth_approval_paykit_access_description)) } } @@ -453,11 +450,13 @@ private fun ColumnScope.SuccessContent( uiState: PubkyAuthApprovalUiState, onDismiss: () -> Unit, ) { - VerticalSpacer(26.dp) + VerticalSpacer(16.dp) SuccessDescriptionText( + clientId = uiState.clientId, serviceName = uiState.serviceName, truncatedKey = uiState.profile?.authDisplayPublicKey.orEmpty(), + modifier = Modifier.padding(horizontal = 16.dp) ) VerticalSpacer(16.dp) @@ -468,7 +467,11 @@ private fun ColumnScope.SuccessContent( contentDescription = null, modifier = Modifier .size(256.dp) - .align(Alignment.CenterHorizontally), + .align(Alignment.CenterHorizontally) + .graphicsLayer { + scaleX = CHECK_SCALE + scaleY = CHECK_SCALE + }, ) FillHeight() @@ -482,56 +485,68 @@ private fun ColumnScope.SuccessContent( } @Composable -private fun DescriptionText(serviceName: String) { - BodyM( - text = stringResource(R.string.profile__auth_approval_service, serviceName) - .withAccentBoldBright(), - color = Colors.White64, - ) +private fun DescriptionText(clientId: String, serviceName: String) { + val text = if (clientId.isNotBlank()) { + stringResource(R.string.profile__auth_approval_service_named, clientId, serviceName) + } else { + stringResource(R.string.profile__auth_approval_service, serviceName) + } + BodyM(text = text.withAccentBoldBright(), color = Colors.White64) } @Composable -private fun SuccessDescriptionText(serviceName: String, truncatedKey: String) { - BodyM( - text = stringResource(R.string.profile__auth_approval_success_detail, truncatedKey, serviceName) - .withAccentBoldBright(), - color = Colors.White64, - ) +private fun SuccessDescriptionText( + clientId: String, + serviceName: String, + truncatedKey: String, + modifier: Modifier = Modifier, +) { + val text = if (clientId.isNotBlank()) { + stringResource(R.string.profile__auth_approval_success_detail_named, truncatedKey, clientId, serviceName) + } else { + stringResource(R.string.profile__auth_approval_success_detail, truncatedKey, serviceName) + } + BodyM(text = text.withAccentBoldBright(), color = Colors.White64, modifier = modifier) } @Composable -private fun PermissionsSection(permissions: ImmutableList) { - Text13Up( - text = stringResource(R.string.profile__auth_approval_permissions), - color = Colors.White64, - ) - VerticalSpacer(8.dp) - - permissions.forEach { permission -> - PermissionRow(permission) - VerticalSpacer(4.dp) +private fun Section( + title: String, + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = modifier.fillMaxWidth() + ) { + Text13Up(text = title, color = Colors.White64) + content() } +} - VerticalSpacer(4.dp) - HorizontalDivider(color = Colors.White10) +@Composable +private fun PermissionsSection(permissions: ImmutableList) { + Section(title = stringResource(R.string.profile__auth_approval_permissions)) { + permissions.forEach { PermissionRow(it) } + } } @Composable private fun PermissionRow(permission: PubkyAuthPermission) { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth() ) { Icon( painter = painterResource(R.drawable.ic_folder), contentDescription = null, tint = Colors.White, - modifier = Modifier.size(14.dp), + modifier = Modifier.size(16.dp) ) HorizontalSpacer(4.dp) BodySSB( text = permission.displayPath, - modifier = Modifier.weight(1f), + modifier = Modifier.weight(1f) ) Text13Up( text = permission.displayAccess, @@ -542,55 +557,65 @@ private fun PermissionRow(permission: PubkyAuthPermission) { @Composable private fun TrustWarning() { - BodyM( - text = stringResource(R.string.profile__auth_approval_trust_warning), - color = Colors.White64, - ) + Section(title = stringResource(R.string.profile__auth_approval_before_continue)) { + BodyS( + text = stringResource(R.string.profile__auth_approval_trust_warning), + color = Colors.White64, + ) + } } @Composable private fun ProfileCard(profile: PubkyProfile) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier .fillMaxWidth() .background(Colors.Gray6, RoundedCornerShape(16.dp)) - .padding(24.dp), + .padding(24.dp) ) { - Text13Up( - text = profile.authDisplayPublicKey, - color = Colors.White64, - ) - VerticalSpacer(16.dp) - if (profile.imageUrl != null) { - PubkyImage(uri = profile.imageUrl, size = 96.dp) + PubkyImage(uri = profile.imageUrl, size = 48.dp) } else { Box( contentAlignment = Alignment.Center, modifier = Modifier - .size(96.dp) + .size(48.dp) .clip(CircleShape) - .background(Colors.Gray5), + .background(Colors.Gray5) ) { Icon( painter = painterResource(R.drawable.ic_user_square), contentDescription = null, tint = Colors.White32, - modifier = Modifier.size(48.dp), + modifier = Modifier.size(24.dp) ) } } - VerticalSpacer(16.dp) - Headline( - text = AnnotatedString(profile.name), - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth(), - ) + Column(modifier = Modifier.weight(1f)) { + Text13Up( + text = profile.authDisplayPublicKey, + color = Colors.White64, + maxLines = 1, + ) + BodyMSB( + text = profile.name, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } } } +/** Figma draws the coin illustration at 329dp inside its 256dp slot, shifted 7dp down. */ +private const val COIN_SCALE = 329f / 256f +private val COIN_OFFSET_Y = 7.dp + +/** Figma draws the check illustration at 274dp inside its 256dp slot. */ +private const val CHECK_SCALE = 274f / 256f + private val PubkyProfile.authDisplayPublicKey: String get() = pubkyAuthDisplayPublicKey(publicKey) @@ -630,12 +655,11 @@ private fun AuthorizePreview() { uiState = PubkyAuthApprovalUiState( state = ApprovalState.Authorize, clientId = "app.paykit.server", - serviceName = "pubky.app", + serviceName = "paykit", permissions = persistentListOf( - PubkyAuthPermission(path = "/pub/pubky.app/", accessLevel = "rw"), - PubkyAuthPermission(path = "/pub/paykit/v0/", accessLevel = "rw"), + PubkyAuthPermission(path = "/pub/paykit/", accessLevel = "rw"), ), - bitkitClaim = PubkyAuthClaim(Item.WATCH_ONLY_ACCOUNT_V1), + bitkitClaim = PubkyAuthClaim(Item.PAYKIT_ACCESS_V1, Item.WATCH_ONLY_ACCOUNT_V1), profile = PubkyProfile( publicKey = "pk8e3qm5f4kgczagxhertyuiop1gxag", name = "Satoshi Nakamoto", @@ -664,7 +688,8 @@ private fun SuccessPreview() { Content( uiState = PubkyAuthApprovalUiState( state = ApprovalState.Success, - serviceName = "pubky.app", + clientId = "app.paykit.server", + serviceName = "paykit", ), isCurrentRequest = true, onAuthorize = {}, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 63748b1ade..89937fc9cf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -654,17 +654,19 @@ For example: \'Developer\' Authorize Authorizing… + Before you continue + Details OK - Read and manage your private Paykit data, and send Paykit messages on your behalf. Your Pubky identity secret and wallet spending keys are not shared. - Paykit access + This service will manage your private Paykit data and send Paykit messages for you. Your pubky secret and wallet keys stay private. Requested permissions - Requester ID: %1$s A service is requesting permission to access and edit your <accent>%1$s</accent> data. + <accent>%1$s</accent> is requesting permission to access and edit your <accent>%2$s</accent> data. Unknown service Authorization Successful You authorized with pubky <accent>%1$s</accent> and gave the service permission to access and edit your <accent>%2$s</accent> data. + You authorized with pubky <accent>%1$s</accent> and gave <accent>%2$s</accent> permission to access and edit your <accent>%3$s</accent> data. Authorize - Make sure you trust the service, browser, or device before authorizing with your pubky. + Make sure you trust the service, browser, and device before authorizing with your pubky. %1$s server Approve To earn, you need to share a watch-only Bitcoin account with Paykit. It can view sales activity, but cannot spend funds. From 9c8dc669e52438cd87e7b501ebd923b4b2490336 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 5 Oct 2026 11:31:16 -0300 Subject: [PATCH 3/8] chore: add auth sheet changelog fragment Co-Authored-By: Claude Opus 5.5 --- changelog.d/next/paykit-auth-figma-parity.changed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/next/paykit-auth-figma-parity.changed.md diff --git a/changelog.d/next/paykit-auth-figma-parity.changed.md b/changelog.d/next/paykit-auth-figma-parity.changed.md new file mode 100644 index 0000000000..6f31052f76 --- /dev/null +++ b/changelog.d/next/paykit-auth-figma-parity.changed.md @@ -0,0 +1 @@ +Pubky authorization dialogs now name the requesting service, group permissions and details under clear headings, and show a compact profile card. From 9ed3c3325fa9bd8d6299f835f430a53ef091f58c Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 5 Oct 2026 11:54:44 -0300 Subject: [PATCH 4/8] fix: scroll auth footer when details do not fit Co-Authored-By: Claude Opus 5.5 --- .../screens/profile/PubkyAuthApprovalSheet.kt | 81 ++++++++++--------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt b/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt index 328a1e5c58..ccc0fa484f 100644 --- a/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt +++ b/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt @@ -4,11 +4,13 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -393,46 +395,53 @@ private fun ColumnScope.AuthorizingContent( private fun ColumnScope.ApprovalDetails( uiState: PubkyAuthApprovalUiState, ) { - Column( - modifier = Modifier - .weight(1f) - .verticalScroll(rememberScrollState()) - ) { - VerticalSpacer(8.dp) - - if (uiState.createsIdentity) { - BodyM(text = stringResource(R.string.pubky_auth__signup_description), color = Colors.White64) - VerticalSpacer(16.dp) - } - if (uiState.permissions.isNotEmpty()) { - DescriptionText(clientId = uiState.clientId, serviceName = uiState.serviceName) - VerticalSpacer(32.dp) - PermissionsSection(permissions = uiState.permissions) - } - if (uiState.bitkitClaim?.includesPaykitAccess == true) { - VerticalSpacer(32.dp) - PaykitAccessSection() - } - VerticalSpacer(16.dp) - } - - TrustWarning() - VerticalSpacer(16.dp) - - uiState.homeserverPublicKey?.takeIf { uiState.createsIdentity }?.let { homeserver -> + BoxWithConstraints(modifier = Modifier.weight(1f)) { Column( - verticalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.SpaceBetween, modifier = Modifier - .fillMaxWidth() - .background(Colors.Gray6, RoundedCornerShape(16.dp)) - .padding(24.dp) - .testTag("PubkySignupHomeserver") + .verticalScroll(rememberScrollState()) + .heightIn(min = maxHeight) ) { - Text13Up(text = stringResource(R.string.pubky_auth__homeserver), color = Colors.White64) - BodyMSB(text = homeserver) + Column { + VerticalSpacer(8.dp) + + if (uiState.createsIdentity) { + BodyM(text = stringResource(R.string.pubky_auth__signup_description), color = Colors.White64) + VerticalSpacer(16.dp) + } + if (uiState.permissions.isNotEmpty()) { + DescriptionText(clientId = uiState.clientId, serviceName = uiState.serviceName) + VerticalSpacer(32.dp) + PermissionsSection(permissions = uiState.permissions) + } + if (uiState.bitkitClaim?.includesPaykitAccess == true) { + VerticalSpacer(32.dp) + PaykitAccessSection() + } + VerticalSpacer(32.dp) + } + + Column { + TrustWarning() + VerticalSpacer(16.dp) + + uiState.homeserverPublicKey?.takeIf { uiState.createsIdentity }?.let { homeserver -> + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .fillMaxWidth() + .background(Colors.Gray6, RoundedCornerShape(16.dp)) + .padding(24.dp) + .testTag("PubkySignupHomeserver") + ) { + Text13Up(text = stringResource(R.string.pubky_auth__homeserver), color = Colors.White64) + BodyMSB(text = homeserver) + } + } ?: uiState.profile?.let { ProfileCard(it) } + VerticalSpacer(24.dp) + } } - } ?: uiState.profile?.let { ProfileCard(it) } - VerticalSpacer(24.dp) + } } @Composable From b3313e30f2f3f69b5ec21083544b81042681e5c1 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 8 Oct 2026 06:44:22 -0300 Subject: [PATCH 5/8] fix: use pubky green for auth profile placeholder avatar Co-Authored-By: Claude Opus 5.5 --- .../java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt b/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt index ccc0fa484f..3bfb58b998 100644 --- a/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt +++ b/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt @@ -592,7 +592,7 @@ private fun ProfileCard(profile: PubkyProfile) { modifier = Modifier .size(48.dp) .clip(CircleShape) - .background(Colors.Gray5) + .background(Colors.PubkyGreen) ) { Icon( painter = painterResource(R.drawable.ic_user_square), From db49bc91decff53eb7da767eb8bb797a284a84a8 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 8 Oct 2026 08:36:50 -0300 Subject: [PATCH 6/8] fix: restore grey auth profile placeholder avatar Co-Authored-By: Claude Opus 5.5 --- .../java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt b/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt index 3bfb58b998..ccc0fa484f 100644 --- a/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt +++ b/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt @@ -592,7 +592,7 @@ private fun ProfileCard(profile: PubkyProfile) { modifier = Modifier .size(48.dp) .clip(CircleShape) - .background(Colors.PubkyGreen) + .background(Colors.Gray5) ) { Icon( painter = painterResource(R.drawable.ic_user_square), From fc7421a764050aa7e569cc4f36fd723a24f22d0f Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 8 Oct 2026 12:58:07 -0300 Subject: [PATCH 7/8] fix: handle long or marked-up requester ids on auth sheet Co-Authored-By: Claude Opus 5.5 --- .../screens/profile/PubkyAuthApprovalSheet.kt | 71 +++++++++++-------- .../profile/PubkyAuthApprovalSheetTest.kt | 7 ++ ...igma-parity.changed.md => 1421.changed.md} | 0 3 files changed, 49 insertions(+), 29 deletions(-) rename changelog.d/next/{paykit-auth-figma-parity.changed.md => 1421.changed.md} (100%) diff --git a/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt b/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt index ccc0fa484f..79181d35db 100644 --- a/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt +++ b/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt @@ -69,6 +69,7 @@ import to.bitkit.ui.shared.util.gradientBackground import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors import to.bitkit.ui.utils.rememberBiometricAuthSupported +import to.bitkit.ui.utils.removeAccentTags import to.bitkit.ui.utils.withAccent import to.bitkit.ui.utils.withAccentBoldBright @@ -459,31 +460,35 @@ private fun ColumnScope.SuccessContent( uiState: PubkyAuthApprovalUiState, onDismiss: () -> Unit, ) { - VerticalSpacer(16.dp) - - SuccessDescriptionText( - clientId = uiState.clientId, - serviceName = uiState.serviceName, - truncatedKey = uiState.profile?.authDisplayPublicKey.orEmpty(), - modifier = Modifier.padding(horizontal = 16.dp) - ) - VerticalSpacer(16.dp) - - FillHeight() + BoxWithConstraints(modifier = Modifier.weight(1f)) { + Column( + verticalArrangement = Arrangement.SpaceBetween, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .verticalScroll(rememberScrollState()) + .heightIn(min = maxHeight) + ) { + SuccessDescriptionText( + clientId = uiState.clientId, + serviceName = uiState.serviceName, + truncatedKey = uiState.profile?.authDisplayPublicKey.orEmpty(), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 16.dp) + ) - Image( - painter = painterResource(R.drawable.check), - contentDescription = null, - modifier = Modifier - .size(256.dp) - .align(Alignment.CenterHorizontally) - .graphicsLayer { - scaleX = CHECK_SCALE - scaleY = CHECK_SCALE - }, - ) + Image( + painter = painterResource(R.drawable.check), + contentDescription = null, + modifier = Modifier + .size(256.dp) + .graphicsLayer { + scaleX = CHECK_SCALE + scaleY = CHECK_SCALE + }, + ) - FillHeight() + VerticalSpacer(16.dp) + } + } PrimaryButton( text = stringResource(R.string.profile__auth_approval_ok), @@ -495,10 +500,12 @@ private fun ColumnScope.SuccessContent( @Composable private fun DescriptionText(clientId: String, serviceName: String) { - val text = if (clientId.isNotBlank()) { - stringResource(R.string.profile__auth_approval_service_named, clientId, serviceName) + val requester = clientId.withoutAccentMarkup() + val service = serviceName.withoutAccentMarkup() + val text = if (requester.isNotBlank()) { + stringResource(R.string.profile__auth_approval_service_named, requester, service) } else { - stringResource(R.string.profile__auth_approval_service, serviceName) + stringResource(R.string.profile__auth_approval_service, service) } BodyM(text = text.withAccentBoldBright(), color = Colors.White64) } @@ -510,10 +517,12 @@ private fun SuccessDescriptionText( truncatedKey: String, modifier: Modifier = Modifier, ) { - val text = if (clientId.isNotBlank()) { - stringResource(R.string.profile__auth_approval_success_detail_named, truncatedKey, clientId, serviceName) + val requester = clientId.withoutAccentMarkup() + val service = serviceName.withoutAccentMarkup() + val text = if (requester.isNotBlank()) { + stringResource(R.string.profile__auth_approval_success_detail_named, truncatedKey, requester, service) } else { - stringResource(R.string.profile__auth_approval_success_detail, truncatedKey, serviceName) + stringResource(R.string.profile__auth_approval_success_detail, truncatedKey, service) } BodyM(text = text.withAccentBoldBright(), color = Colors.White64, modifier = modifier) } @@ -628,6 +637,10 @@ private const val CHECK_SCALE = 274f / 256f private val PubkyProfile.authDisplayPublicKey: String get() = pubkyAuthDisplayPublicKey(publicKey) +/** Request data is shown literally: accent tags inside it must not be read as markup by [withAccentBoldBright]. */ +internal fun String.withoutAccentMarkup(): String = + generateSequence(this) { current -> current.removeAccentTags().takeIf { it != current } }.last() + internal fun pubkyAuthDisplayPublicKey(publicKey: String): String { val rawKey = publicKey.removePrefix("pubky") return if (rawKey.length > 8) "${rawKey.take(4)}...${rawKey.takeLast(4)}" else rawKey diff --git a/app/src/test/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheetTest.kt b/app/src/test/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheetTest.kt index b1c1dec988..44213df329 100644 --- a/app/src/test/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheetTest.kt +++ b/app/src/test/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheetTest.kt @@ -48,4 +48,11 @@ class PubkyAuthApprovalSheetTest { assertEquals(PubkyApprovalLocalAuthMode.None, result) } + + @Test + fun `request text loses accent markup even when tags are nested`() { + assertEquals("evil.app", "evil.app".withoutAccentMarkup()) + assertEquals("evil.app", "ent>evilent>.app".withoutAccentMarkup()) + assertEquals("app.paykit.server", "app.paykit.server".withoutAccentMarkup()) + } } diff --git a/changelog.d/next/paykit-auth-figma-parity.changed.md b/changelog.d/next/1421.changed.md similarity index 100% rename from changelog.d/next/paykit-auth-figma-parity.changed.md rename to changelog.d/next/1421.changed.md From fd722c3306e18e50e273beec7933a29fc6055f5d Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 12 Oct 2026 08:20:15 -0300 Subject: [PATCH 8/8] fix: drop control and bidi characters from auth requester text Co-Authored-By: Claude Opus 5.5 --- .../screens/profile/PubkyAuthApprovalSheet.kt | 31 ++++++++++++++----- .../profile/PubkyAuthApprovalSheetTest.kt | 14 +++++++-- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt b/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt index 79181d35db..58afffee44 100644 --- a/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt +++ b/app/src/main/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheet.kt @@ -500,8 +500,8 @@ private fun ColumnScope.SuccessContent( @Composable private fun DescriptionText(clientId: String, serviceName: String) { - val requester = clientId.withoutAccentMarkup() - val service = serviceName.withoutAccentMarkup() + val requester = clientId.asLiteralRequestText() + val service = serviceName.asLiteralRequestText() val text = if (requester.isNotBlank()) { stringResource(R.string.profile__auth_approval_service_named, requester, service) } else { @@ -517,8 +517,8 @@ private fun SuccessDescriptionText( truncatedKey: String, modifier: Modifier = Modifier, ) { - val requester = clientId.withoutAccentMarkup() - val service = serviceName.withoutAccentMarkup() + val requester = clientId.asLiteralRequestText() + val service = serviceName.asLiteralRequestText() val text = if (requester.isNotBlank()) { stringResource(R.string.profile__auth_approval_success_detail_named, truncatedKey, requester, service) } else { @@ -637,9 +637,26 @@ private const val CHECK_SCALE = 274f / 256f private val PubkyProfile.authDisplayPublicKey: String get() = pubkyAuthDisplayPublicKey(publicKey) -/** Request data is shown literally: accent tags inside it must not be read as markup by [withAccentBoldBright]. */ -internal fun String.withoutAccentMarkup(): String = - generateSequence(this) { current -> current.removeAccentTags().takeIf { it != current } }.last() +/** Unicode categories that are dropped from request data: line breaks, bidi and other invisible controls. */ +private val HIDDEN_CHARACTER_TYPES = setOf( + Character.CONTROL, + Character.FORMAT, + Character.LINE_SEPARATOR, + Character.PARAGRAPH_SEPARATOR, +) + +/** + * Request data is shown literally: control and bidi characters are dropped so it cannot add lines or reorder the + * sentence, and accent tags inside it must not be read as markup by [withAccentBoldBright]. + */ +internal fun String.asLiteralRequestText(): String { + val visible = buildString { + this@asLiteralRequestText.codePoints() + .filter { Character.getType(it).toByte() !in HIDDEN_CHARACTER_TYPES } + .forEach { appendCodePoint(it) } + } + return generateSequence(visible) { current -> current.removeAccentTags().takeIf { it != current } }.last() +} internal fun pubkyAuthDisplayPublicKey(publicKey: String): String { val rawKey = publicKey.removePrefix("pubky") diff --git a/app/src/test/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheetTest.kt b/app/src/test/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheetTest.kt index 44213df329..d71e3241b0 100644 --- a/app/src/test/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheetTest.kt +++ b/app/src/test/java/to/bitkit/ui/screens/profile/PubkyAuthApprovalSheetTest.kt @@ -51,8 +51,16 @@ class PubkyAuthApprovalSheetTest { @Test fun `request text loses accent markup even when tags are nested`() { - assertEquals("evil.app", "evil.app".withoutAccentMarkup()) - assertEquals("evil.app", "ent>evilent>.app".withoutAccentMarkup()) - assertEquals("app.paykit.server", "app.paykit.server".withoutAccentMarkup()) + assertEquals("evil.app", "evil.app".asLiteralRequestText()) + assertEquals("evil.app", "ent>evilent>.app".asLiteralRequestText()) + assertEquals("app.paykit.server", "app.paykit.server".asLiteralRequestText()) + } + + @Test + fun `request text loses line breaks and bidi controls`() { + assertEquals("bank.comBitkit is safe.", "bank.com\nBitkit is safe.".asLiteralRequestText()) + assertEquals("moc.knab", "\u202Emoc.knab\u202C".asLiteralRequestText()) + assertEquals("abcd", "a\u2028b\u2066c\u200Bd".asLiteralRequestText()) + assertEquals("evil", "evil".asLiteralRequestText()) } }