From 50f870faa4f97252540ef5e4704e629a8564ef41 Mon Sep 17 00:00:00 2001 From: Nick Klockenga Date: Mon, 6 Apr 2026 22:44:31 -0400 Subject: [PATCH] Feature to format bitcoin addresses in 4 character chunks (#17) * format bitcoin addresses in 4 character chunks for increased readability * add formating to verify wallet screen --- hellbender/Extensions/BDK+Extensions.swift | 24 +++++++++++++++++++ .../Main/Receive/AddressDetailView.swift | 4 +--- .../Views/Main/Receive/ReceiveView.swift | 4 +--- .../Views/Main/Send/PSBTDisplayView.swift | 10 ++------ .../Views/Main/Send/SendReviewView.swift | 10 ++------ .../Views/Main/Settings/UTXODetailView.swift | 4 +--- hellbender/Views/Setup/WalletVerifyView.swift | 4 +--- 7 files changed, 32 insertions(+), 28 deletions(-) diff --git a/hellbender/Extensions/BDK+Extensions.swift b/hellbender/Extensions/BDK+Extensions.swift index 5e2fb1c..ae4671c 100644 --- a/hellbender/Extensions/BDK+Extensions.swift +++ b/hellbender/Extensions/BDK+Extensions.swift @@ -1,4 +1,5 @@ import Foundation +import SwiftUI // MARK: - Denomination @@ -57,6 +58,29 @@ extension String { guard count > leading + trailing + 3 else { return self } return "\(prefix(leading))...\(suffix(trailing))" } + + /// Build a styled Text view with space-separated 4-character chunks, + /// alternating between primary and secondary text colors. + func chunkedAddressText(font: Font = .hbMono(13)) -> Text { + var chunks: [String] = [] + var current = "" + for (i, char) in enumerated() { + if i > 0, i % 4 == 0 { + chunks.append(current) + current = "" + } + current.append(char) + } + if !current.isEmpty { chunks.append(current) } + + var result = Text("") + for (i, chunk) in chunks.enumerated() { + if i > 0 { result = result + Text(" ") } + let color: Color = i % 2 == 0 ? .hbTextPrimary : .hbTextSecondary + result = result + Text(chunk).font(font).foregroundColor(color) + } + return result + } } extension Date { diff --git a/hellbender/Views/Main/Receive/AddressDetailView.swift b/hellbender/Views/Main/Receive/AddressDetailView.swift index 6b01b21..b893403 100644 --- a/hellbender/Views/Main/Receive/AddressDetailView.swift +++ b/hellbender/Views/Main/Receive/AddressDetailView.swift @@ -26,9 +26,7 @@ struct AddressDetailView: View { .font(.hbLabel()) .foregroundStyle(Color.hbTextSecondary) - Text(address) - .font(.hbMono(13)) - .foregroundStyle(Color.hbTextPrimary) + address.chunkedAddressText() .multilineTextAlignment(.center) .padding(.horizontal, 32) .textSelection(.enabled) diff --git a/hellbender/Views/Main/Receive/ReceiveView.swift b/hellbender/Views/Main/Receive/ReceiveView.swift index f059900..8cf824a 100644 --- a/hellbender/Views/Main/Receive/ReceiveView.swift +++ b/hellbender/Views/Main/Receive/ReceiveView.swift @@ -37,9 +37,7 @@ struct ReceiveView: View { .font(.hbLabel()) .foregroundStyle(Color.hbTextSecondary) - Text(viewModel.currentAddress) - .font(.hbMono(13)) - .foregroundStyle(Color.hbTextPrimary) + viewModel.currentAddress.chunkedAddressText() .multilineTextAlignment(.center) .padding(.horizontal, 32) .textSelection(.enabled) diff --git a/hellbender/Views/Main/Send/PSBTDisplayView.swift b/hellbender/Views/Main/Send/PSBTDisplayView.swift index 34025da..f4bebdd 100644 --- a/hellbender/Views/Main/Send/PSBTDisplayView.swift +++ b/hellbender/Views/Main/Send/PSBTDisplayView.swift @@ -338,17 +338,11 @@ private struct PSBTReviewCard: View { ForEach(Array(viewModel.recipients.enumerated()), id: \.element.id) { index, recipient in if viewModel.recipients.count > 1 { ReviewItem(label: "Recipient \(index + 1)") { - Text(recipient.address) - .font(.hbMono(12)) - .foregroundStyle(Color.hbTextPrimary) - .lineLimit(2) + recipient.address.chunkedAddressText(font: .hbMono(12)) } } else { ReviewItem(label: "To") { - Text(recipient.address) - .font(.hbMono(12)) - .foregroundStyle(Color.hbTextPrimary) - .lineLimit(2) + recipient.address.chunkedAddressText(font: .hbMono(12)) } } diff --git a/hellbender/Views/Main/Send/SendReviewView.swift b/hellbender/Views/Main/Send/SendReviewView.swift index 80a194f..359d883 100644 --- a/hellbender/Views/Main/Send/SendReviewView.swift +++ b/hellbender/Views/Main/Send/SendReviewView.swift @@ -24,17 +24,11 @@ struct SendReviewView: View { ForEach(Array(viewModel.recipients.enumerated()), id: \.element.id) { index, recipient in if viewModel.recipients.count > 1 { ReviewItem(label: "Recipient \(index + 1)") { - Text(recipient.address) - .font(.hbMono(12)) - .foregroundStyle(Color.hbTextPrimary) - .lineLimit(2) + recipient.address.chunkedAddressText(font: .hbMono(12)) } } else { ReviewItem(label: "To") { - Text(recipient.address) - .font(.hbMono(12)) - .foregroundStyle(Color.hbTextPrimary) - .lineLimit(2) + recipient.address.chunkedAddressText(font: .hbMono(12)) } } diff --git a/hellbender/Views/Main/Settings/UTXODetailView.swift b/hellbender/Views/Main/Settings/UTXODetailView.swift index 8bc0cdc..e89e199 100644 --- a/hellbender/Views/Main/Settings/UTXODetailView.swift +++ b/hellbender/Views/Main/Settings/UTXODetailView.swift @@ -110,9 +110,7 @@ struct UTXODetailView: View { .font(.hbMono(12)) .foregroundStyle(Color.hbTextPrimary) } else { - Text(address) - .font(.hbMono(12)) - .foregroundStyle(Color.hbTextPrimary) + address.chunkedAddressText(font: .hbMono(12)) .textSelection(.enabled) } } diff --git a/hellbender/Views/Setup/WalletVerifyView.swift b/hellbender/Views/Setup/WalletVerifyView.swift index b9fc1c1..1371ab2 100644 --- a/hellbender/Views/Setup/WalletVerifyView.swift +++ b/hellbender/Views/Setup/WalletVerifyView.swift @@ -162,9 +162,7 @@ struct WalletVerifyView: View { .clipShape(RoundedRectangle(cornerRadius: 8)) .frame(maxWidth: .infinity) - Text(viewModel.firstReceiveAddress) - .font(.hbMono(12)) - .foregroundStyle(Color.hbTextPrimary) + viewModel.firstReceiveAddress.chunkedAddressText(font: .hbMono(12)) .multilineTextAlignment(.center) .frame(maxWidth: .infinity) .textSelection(.enabled)