Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Bitkit/Components/Scanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,15 @@ struct Scanner: View {
ZStack {
if cameraManager.hasPermission {
#if targetEnvironment(simulator)
Color.black
Color.black.allowsHitTesting(false)
#else
ScannerCamera(
isTorchOn: isTorchOn,
onScan: { payload in
await onScan(payload)
}
)
.allowsHitTesting(false)
#endif

ScannerCornerButtons(
Expand All @@ -264,6 +265,7 @@ struct Scanner: View {
}
}
.cornerRadius(16)
.contentShape(Rectangle())
.onAppear {
guard !cameraManager.hasPermission else { return }
cameraManager.requestPermissionIfNeeded()
Expand Down
40 changes: 27 additions & 13 deletions Bitkit/Views/PaymentRequests/PaymentRequestsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,31 +172,45 @@ struct PaymentRequestCard: View {
}

private var header: some View {
ViewThatFits(in: .horizontal) {
HStack(spacing: 16) {
contactSummary.fixedSize(horizontal: true, vertical: false)
Comment thread
ben-kaufman marked this conversation as resolved.
Spacer(minLength: 8)
amountSummary.fixedSize(horizontal: true, vertical: false)
}
VStack(alignment: .trailing, spacing: 8) {
contactSummary.frame(maxWidth: .infinity, alignment: .leading)
amountSummary.fixedSize(horizontal: false, vertical: true)
}
}
.padding(16)
.contentShape(Rectangle())
}

private var contactSummary: some View {
HStack(spacing: 16) {
avatar

VStack(alignment: .leading, spacing: 4) {
BodyMSBText(title)
.lineLimit(1)
CaptionText(subtitle, textColor: .white64)
.lineLimit(1)
}
}
}

Spacer(minLength: 8)

if let amountStatus = receipt?.statusText ?? amountStatus ??
(request.direction == .outgoing && request.paymentProofKind == .usdt ? t("wallet__payment_request_status_pending") : nil)
{
VStack(alignment: .trailing, spacing: 2) {
PaykitAmountText(amount: request.amount, prefix: amountPrefix)
CaptionText(amountStatus, textColor: .white64)
}
} else {
@ViewBuilder
private var amountSummary: some View {
if let amountStatus = receipt?.statusText ?? amountStatus ??
(request.direction == .outgoing && request.paymentProofKind == .usdt ? t("wallet__payment_request_status_pending") : nil)
{
VStack(alignment: .trailing, spacing: 2) {
PaykitAmountText(amount: request.amount, prefix: amountPrefix)
CaptionText(amountStatus, textColor: .white64)
}
} else {
PaykitAmountText(amount: request.amount, prefix: amountPrefix)
}
.padding(16)
.contentShape(Rectangle())
}

@ViewBuilder
Expand Down
11 changes: 7 additions & 4 deletions Bitkit/Views/Wallets/Activity/ActivityRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,23 @@ struct ActivityRowContent<Icon: View, Amount: View>: View {
var titleLineHeight: CGFloat?
var subtitleLineHeight: CGFloat?
var textSpacing: CGFloat = 2
var stacked = false
@ViewBuilder var icon: () -> Icon
@ViewBuilder var amount: () -> Amount

var body: some View {
HStack(spacing: 16) {
let layout = stacked ? AnyLayout(VStackLayout(alignment: .leading, spacing: 16)) : AnyLayout(HStackLayout(spacing: 16))
layout {
icon()
VStack(alignment: .leading, spacing: textSpacing) {
BodyMSBText(title, kerning: textKerning, lineHeight: titleLineHeight).lineLimit(1)
CaptionBText(subtitle, kerning: textKerning, lineHeight: subtitleLineHeight).lineLimit(1)
BodyMSBText(title, kerning: textKerning, lineHeight: titleLineHeight).lineLimit(stacked ? nil : 1)
CaptionBText(subtitle, kerning: textKerning, lineHeight: subtitleLineHeight).lineLimit(stacked ? nil : 1)
}
.fixedSize(horizontal: false, vertical: true)
Spacer(minLength: 0)
if !stacked { Spacer(minLength: 0) }
Comment thread
ben-kaufman marked this conversation as resolved.
amount()
}
.frame(maxWidth: stacked ? .infinity : nil, alignment: .leading)
}
}

Expand Down
14 changes: 10 additions & 4 deletions Bitkit/Views/Wallets/UsdtDepositHistoryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,10 @@ struct UsdtDepositHistoryView: View {
}
UsdtSupportActions(details: detail.supportDetails).disabled(busy || recoveryActive)
} else {
HStack(alignment: .top, spacing: 16) {
let layout = dynamicTypeSize.isAccessibilitySize
? AnyLayout(VStackLayout(alignment: .leading, spacing: 16))
: AnyLayout(HStackLayout(alignment: .top, spacing: 16))
layout {
UsdtDetailCell(title: t("wallet__activity_status"), value: detail.statusText, icon: detail.statusIcon,
valueColor: detail.statusColor, iconColor: detail.statusColor, valueKerning: 0)
UsdtDetailCell(title: t("usdt__receiving_network"), value: UsdtDestination.arbitrum.label, icon: "usdt-network")
Expand Down Expand Up @@ -412,6 +415,7 @@ extension UsdtDeposit {
}

struct UsdtDepositRow: View {
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
let deposit: UsdtDeposit
let hideBalance: Bool

Expand All @@ -424,14 +428,16 @@ struct UsdtDepositRow: View {
let color: Color = completed ? .usdtAccent : .yellowAccent
ActivityRowContainer {
ActivityRowContent(title: deposit.networkLabel, subtitle: deposit.statusText, textKerning: 0,
titleLineHeight: 22, subtitleLineHeight: 18, textSpacing: 0)
titleLineHeight: 22, subtitleLineHeight: 18, textSpacing: 0, stacked: dynamicTypeSize.isAccessibilitySize)
{
CircularIcon(icon: completed ? "check-circle" : "hourglass-simple", iconColor: color,
backgroundColor: color.opacity(0.16), size: 40)
} amount: {
VStack(alignment: .trailing, spacing: 0) {
BodyMSBText(deposit.asset == "USDT" ? amount + " USDT" : deposit.asset, kerning: 0, lineHeight: 22).lineLimit(1)
VStack(alignment: dynamicTypeSize.isAccessibilitySize ? .leading : .trailing, spacing: 0) {
BodyMSBText(deposit.asset == "USDT" ? amount + " USDT" : deposit.asset, kerning: 0, lineHeight: 22)
.lineLimit(dynamicTypeSize.isAccessibilitySize ? nil : 1)
.minimumScaleFactor(0.7)
.fixedSize(horizontal: false, vertical: true)
if deposit.asset == "USDT" {
CaptionBText(hideBalance ? amount : "$" + amount, textColor: .textSecondary, kerning: 0, lineHeight: 18)
}
Expand Down
34 changes: 34 additions & 0 deletions BitkitTests/ComponentSizingTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@testable import Bitkit
import BitkitCore
import SwiftUI
import XCTest

Expand Down Expand Up @@ -34,6 +35,39 @@ final class ComponentSizingTests: XCTestCase {
XCTAssertGreaterThan(height(of: permission, width: 280, size: .accessibility5), height(of: permission, width: 280, size: .large))
}

func testDepositRowsGrowToShowNetworkStatusAndAmountAtAccessibilitySizes() {
let deposit = UsdtDeposit(id: "deposit", network: "bsc", asset: "USDT", amount: 50_000_001,
sourceTx: "0x" + String(repeating: "ab", count: 32), status: "held",
code: "standing_route_unavailable", refundTx: nil)
let row = UsdtDepositRow(deposit: deposit, hideBalance: false)
let amount = usdtOverviewAmount(50_000_001)
for width: CGFloat in [280, 320] {
let textWidth = width - 32
let networkHeight = height(of: BodyMSBText(deposit.networkLabel, kerning: 0, lineHeight: 22)
.fixedSize(horizontal: false, vertical: true), width: textWidth, size: .accessibility5)
let statusHeight = height(of: CaptionBText(deposit.statusText, kerning: 0, lineHeight: 18)
.fixedSize(horizontal: false, vertical: true), width: textWidth, size: .accessibility5)
let amountHeight = height(of: BodyMSBText(amount + " USDT", kerning: 0, lineHeight: 22)
.fixedSize(horizontal: false, vertical: true), width: textWidth, size: .accessibility5)
let fiatHeight = height(of: CaptionBText("$" + amount, textColor: .textSecondary, kerning: 0, lineHeight: 18)
.fixedSize(horizontal: false, vertical: true), width: textWidth, size: .accessibility5)
let contentHeight = 40 + networkHeight + statusHeight + amountHeight + fiatHeight
XCTAssertEqual(height(of: row, width: width, size: .accessibility5), contentHeight + 64, accuracy: 1)
}
}

func testDepositRowsUseTheFullAvailableWidthWithLargeText() {
for network in ["polygon", "tron"] {
let deposit = UsdtDeposit(id: "deposit", network: network, asset: "USDT", amount: 1_000_000,
sourceTx: "0x" + String(repeating: "ab", count: 32), status: "pending", code: nil, refundTx: nil)
let row = UsdtDepositRow(deposit: deposit, hideBalance: false)
for width: CGFloat in [280, 320] {
let controller = UIHostingController(rootView: row.environment(\.dynamicTypeSize, .accessibility1))
XCTAssertEqual(controller.sizeThatFits(in: CGSize(width: width, height: 10000)).width, width, accuracy: 1)
}
}
}

private func height(of view: some View, width: CGFloat, size: DynamicTypeSize) -> CGFloat {
let controller = UIHostingController(rootView: view.environment(\.dynamicTypeSize, size))
return controller.sizeThatFits(in: CGSize(width: width, height: 10000)).height
Expand Down
1 change: 1 addition & 0 deletions changelog.d/next/983.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improved USDT deposit and payment request readability and recipient scrolling with enlarged text.
10 changes: 10 additions & 0 deletions journeys/payment-requests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ correctly prevents it from entering the presentation queue.
## Request summary

`request-summary.xml` is ported alongside Android's matching journey.
The USDT history step in `request-summary.xml` requires both instances opted in, a payer funded with
local test USDT, and two completed requests (one USD-denominated and one BTC-denominated), each paid
in USDT with a verified receipt. Establish these using the local USDT fixture before the journey and
record the USD request amount and each payer activity detail's rounded Payment value. The USD
receipt matches the request amount. The BTC request's receipt retains up to six decimals and rounds
to that Payment value (shown as less than 0.01 below one cent), not the fee-inclusive activity total.
The two payments need not have equal amounts. The iOS layout
check uses a supported 375-point-wide device with enlarged Dynamic Type, while Android uses 320 dp.
`contact-request-or-pay.xml` also needs a third linked contact that publishes no payment endpoints,
so the failure and retry steps are reproducible.

`success-dismissal.xml` matches Android's journey: after paying a Lightning request, closing success
must leave no error toast or duplicate payment; closing a later unpaid request must not send it.
Expand Down
3 changes: 2 additions & 1 deletion journeys/payment-requests/contact-request-or-pay.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<journey name="Contact Request Or Pay">
<description>
Verifies that Pay on a linked contact offers Request or Pay, that paying shows progress on the sheet until the amount screen opens within a few seconds, and that Request opens the Payment Request amount screen. Requires two Bitkit instances saved as each other's contacts and linked as identities, with the payer funded; see README.md. Let the payer run for a minute after launch before starting, so start-up Paykit work does not dominate the timings. Open the contact with xcrun simctl openurl booted "bitkit://contact?pubky=&lt;requester-public-key&gt;".
Verifies that Pay on a linked contact offers Request or Pay, that paying shows progress on the sheet until the amount screen opens within a few seconds, and that Request opens the Payment Request amount screen. Requires two Bitkit instances saved as each other's contacts and linked as identities, with the payer funded; see README.md. Also save a third linked contact that publishes no payment endpoints, for the resolution-failure check. Let the payer run for a minute after launch before starting, so start-up Paykit work does not dominate the timings. Open the contact with xcrun simctl openurl booted "bitkit://contact?pubky=&lt;requester-public-key&gt;".
</description>
<actions>
<action>Open bitkit://contact?pubky=&lt;requester-public-key&gt; on the payer</action>
Expand All @@ -16,5 +16,6 @@
<action>Verify the Request or Pay sheet (id "RequestOrPaySheet") appears</action>
<action>Tap Request on the sheet</action>
<action>Verify the Payment Request amount screen (id "PaymentRequestAmount") opens</action>
<action>Open Send and choose Contact. Tap a saved contact and verify its row shows loading feedback while payment details resolve; other contacts cannot be selected meanwhile. When resolution finishes, verify the amount screen opens. Cancel without paying. Reopen Send, choose Contact and select the prepared contact with no payment endpoints. Verify the error is shown and contact selection becomes available for retry. Select the original payable contact and verify its amount screen opens, then cancel without paying.</action>
</actions>
</journey>
3 changes: 2 additions & 1 deletion journeys/payment-requests/request-summary.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<journey name="Payment Request Summary">
<description>
Verifies that the collapsed Payment Request confirmation shows who the request is from and what it is for, keeps the note in the details, and shows "Not specified" in For when the request has no note. Requires two Bitkit instances saved as each other's contacts and linked as identities, with the payer funded to cover 21,000 sats; see README.md.
Verifies that the collapsed Payment Request confirmation shows who the request is from and what it is for, keeps the note in the details, and shows "Not specified" in For when the request has no note. Requires two Bitkit instances saved as each other's contacts and linked as identities, with the payer funded to cover 21,000 sats plus fees and local test USDT payments. Before starting, use the local USDT fixture to establish two completed requests in the requester's history: one USD-denominated and one BTC-denominated, each paid in USDT with a verified receipt. Record the USD request amount and each payer activity detail's rounded Payment value for comparison; the amounts need not be equal. Both instances must have USDT enabled. The iOS layout check uses a supported 375-point-wide device with enlarged Dynamic Type; Android uses 320 dp. See README.md.
</description>
<actions>
<action>On the requester, send the payer contact a Payment Request for 21,000 sats with the note "Lunch last week"</action>
Expand All @@ -15,5 +15,6 @@
<action>On the requester, send the payer contact a Payment Request for 5,000 sats with no note</action>
<action>On the payer, verify the Payment Request confirmation screen (id "PaymentRequestConfirm") appears automatically with 5,000 sats</action>
<action>Verify From (id "PaymentRequestFrom") shows the requester's contact name and For (id "PaymentRequestFor") shows "Not specified"</action>
<action>Open the prepared USD-denominated request in payment history. On a 375-point-wide iOS screen with enlarged text and a long translated verification status, verify a short contact name such as Maria and every status word remain complete. The amount and receipt may move below the contact when needed. Repeat at the default text size and verify short amounts and statuses remain beside the contact when they fit. For the USD request, verify the receipt equals the request amount. Repeat the layout checks for the prepared BTC-denominated request paid in USDT. Verify its receipt shows the USDT amount with up to six decimals in the selected locale's separator and rounds to the Payment value in the payer's activity detail (shown as less than 0.01 below one cent). Do not compare with the fee-inclusive activity total.</action>
</actions>
</journey>
1 change: 1 addition & 0 deletions journeys/usdt-bridge-details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
<action>Open USDT Receive, then Network. Verify Cross-Network Deposits (id "UsdtDepositHistory") is a full-width primary button pinned over the bottom of the network list. Scroll the list and verify the rows and artwork pass behind the button while it stays fixed. Scroll to the end and verify the last content can be brought above the button. Repeat with large text, verify the button label is not clipped, then tap it and verify deposit history opens before any Arbitrum receipt exists.</action>
<action>Using only an isolated refund fixture, submit a refund address that the fixture rejects. Verify UsdtRefundError appears immediately below UsdtRefundAddress and remains visible through two background detail refreshes. Edit the address and verify the error clears; retry a rejected address and verify it returns. Change the selected deposit and verify the previous refund error is absent.</action>
<action>At the largest system text size, open Details on a refundable deposit and dismiss the keyboard. Verify Request refund, Hide details, Contact support and Copy details stack vertically, can be scrolled into view and have complete readable labels. Return to normal text size and verify the paired actions share their original rows.</action>
<action>On iOS, select the largest accessibility text size. Open Cross-Network Deposits and verify each row shows the full source network, status and amount without truncation. Open a deposit and verify Status and Receiving Network stack vertically and remain readable. Return to the default text size and verify the compact row and two-column summary return.</action>
</actions>
</journey>
1 change: 1 addition & 0 deletions journeys/usdt-payment-links-and-scans.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
<action>On a compact display with large text, open USDT Send and select address scanning. Verify the camera remains usable with one scanner heading, one paste action, gallery import and flash controls. Verify denied camera permission offers its normal recovery action and the rest of the send controls remain reachable by scrolling. Cancel without paying.</action>
<action>At the largest system text size, open USDT Send on a compact display. Verify the selected network name is fully readable. Deny camera permission and scroll to Enable camera; verify its full label is visible and the action remains usable. Select BNB Smart Chain when available and verify its complete network name is readable. Cancel without paying.</action>
<action>On iOS with Read Clipboard enabled, open Send, then copy an owned USDT address while the sheet is open. Tap Paste Invoice and allow the system paste permission if requested. Close from Amount without opening a text field, return Home, then background and foreground the app. Verify the already-pasted address is not offered again. Repeat using USDT Send's Paste button. Copy a different supported address and verify automatic detection still offers the new content. Cancel without paying.</action>
<action>On iOS with camera permission enabled and the largest accessibility text size, open USDT Send. Start an upward drag inside the camera preview and verify the recipient screen scrolls so Paste, Enter Manually and the network warning are reachable. Verify the photo picker and flashlight controls still respond. Restore the default text size without sending.</action>
</actions>
</journey>
Loading
Loading