Merge remote-tracking branches 'github/pr/3066' and 'github/pr/3065'

This commit is contained in:
William Casarin
2025-06-02 07:01:35 -07:00
9 changed files with 121 additions and 61 deletions

View File

@@ -115,7 +115,10 @@ class UserSettingsStore: ObservableObject {
@Setting(key: "dismiss_wallet_high_balance_warning", default_value: false) @Setting(key: "dismiss_wallet_high_balance_warning", default_value: false)
var dismiss_wallet_high_balance_warning: Bool var dismiss_wallet_high_balance_warning: Bool
@Setting(key: "hide_wallet_balance", default_value: false)
var hide_wallet_balance: Bool
@Setting(key: "left_handed", default_value: false) @Setting(key: "left_handed", default_value: false)
var left_handed: Bool var left_handed: Bool

View File

@@ -35,7 +35,6 @@ struct SuggestedUserView: View {
let target = FollowTarget.pubkey(user.pubkey) let target = FollowTarget.pubkey(user.pubkey)
InnerProfilePicView(url: user.pfp, InnerProfilePicView(url: user.pfp,
fallbackUrl: nil, fallbackUrl: nil,
pubkey: target.pubkey,
size: 50, size: 50,
highlight: .none, highlight: .none,
disable_animation: false) disable_animation: false)

View File

@@ -31,7 +31,6 @@ func pfp_line_width(_ h: Highlight) -> CGFloat {
struct InnerProfilePicView: View { struct InnerProfilePicView: View {
let url: URL? let url: URL?
let fallbackUrl: URL? let fallbackUrl: URL?
let pubkey: Pubkey
let size: CGFloat let size: CGFloat
let highlight: Highlight let highlight: Highlight
let disable_animation: Bool let disable_animation: Bool
@@ -65,16 +64,19 @@ struct InnerProfilePicView: View {
struct ProfilePicView: View { struct ProfilePicView: View {
@Environment(\.redactionReasons) var redactionReasons
let pubkey: Pubkey let pubkey: Pubkey
let size: CGFloat let size: CGFloat
let highlight: Highlight let highlight: Highlight
let profiles: Profiles let profiles: Profiles
let disable_animation: Bool let disable_animation: Bool
let zappability_indicator: Bool let zappability_indicator: Bool
let privacy_sensitive: Bool
@State var picture: String? @State var picture: String?
init(pubkey: Pubkey, size: CGFloat, highlight: Highlight, profiles: Profiles, disable_animation: Bool, picture: String? = nil, show_zappability: Bool? = nil) { init(pubkey: Pubkey, size: CGFloat, highlight: Highlight, profiles: Profiles, disable_animation: Bool, picture: String? = nil, show_zappability: Bool? = nil, privacy_sensitive: Bool = false) {
self.pubkey = pubkey self.pubkey = pubkey
self.profiles = profiles self.profiles = profiles
self.size = size self.size = size
@@ -82,15 +84,24 @@ struct ProfilePicView: View {
self._picture = State(initialValue: picture) self._picture = State(initialValue: picture)
self.disable_animation = disable_animation self.disable_animation = disable_animation
self.zappability_indicator = show_zappability ?? false self.zappability_indicator = show_zappability ?? false
self.privacy_sensitive = privacy_sensitive
} }
var privacy_sensitive_pubkey: Pubkey {
if privacy_sensitive && redactionReasons.contains(.privacy) {
ANON_PUBKEY
} else {
pubkey
}
}
func get_lnurl() -> String? { func get_lnurl() -> String? {
return profiles.lookup_with_timestamp(pubkey)?.unsafeUnownedValue?.lnurl return profiles.lookup_with_timestamp(pubkey)?.unsafeUnownedValue?.lnurl
} }
var body: some View { var body: some View {
ZStack (alignment: Alignment(horizontal: .trailing, vertical: .bottom)) { ZStack (alignment: Alignment(horizontal: .trailing, vertical: .bottom)) {
InnerProfilePicView(url: get_profile_url(picture: picture, pubkey: pubkey, profiles: profiles), fallbackUrl: URL(string: robohash(pubkey)), pubkey: pubkey, size: size, highlight: highlight, disable_animation: disable_animation) InnerProfilePicView(url: get_profile_url(picture: picture, pubkey: privacy_sensitive_pubkey, profiles: profiles), fallbackUrl: URL(string: robohash(privacy_sensitive_pubkey)), size: size, highlight: highlight, disable_animation: disable_animation)
.onReceive(handle_notify(.profile_updated)) { updated in .onReceive(handle_notify(.profile_updated)) { updated in
guard updated.pubkey == self.pubkey else { guard updated.pubkey == self.pubkey else {
return return

View File

@@ -89,6 +89,7 @@ struct KeySettingsView: View {
.disabled(true) .disabled(true)
} else { } else {
Text(sec) Text(sec)
.privacySensitive()
.clipShape(RoundedRectangle(cornerRadius: 5)) .clipShape(RoundedRectangle(cornerRadius: 5))
} }

View File

@@ -67,6 +67,8 @@ struct ZapSettingsView: View {
Section(NSLocalizedString("NWC wallet", comment: "Title for section in zap settings that controls general NWC wallet settings.")) { Section(NSLocalizedString("NWC wallet", comment: "Title for section in zap settings that controls general NWC wallet settings.")) {
Toggle(NSLocalizedString("Disable high balance warning", comment: "Setting to disable high balance warnings on the user's wallet"), isOn: $settings.dismiss_wallet_high_balance_warning) Toggle(NSLocalizedString("Disable high balance warning", comment: "Setting to disable high balance warnings on the user's wallet"), isOn: $settings.dismiss_wallet_high_balance_warning)
.toggleStyle(.switch) .toggleStyle(.switch)
Toggle(NSLocalizedString("Hide balance", comment: "Setting to hide wallet balance."), isOn: $settings.hide_wallet_balance)
.toggleStyle(.switch)
} }
} }
.navigationTitle(NSLocalizedString("Zaps", comment: "Navigation title for zap settings.")) .navigationTitle(NSLocalizedString("Zaps", comment: "Navigation title for zap settings."))

View File

@@ -9,7 +9,9 @@ import SwiftUI
struct BalanceView: View { struct BalanceView: View {
var balance: Int64? var balance: Int64?
@Binding var hide_balance: Bool
var body: some View { var body: some View {
VStack(spacing: 5) { VStack(spacing: 5) {
Text("Current balance", comment: "Label for displaying current wallet balance") Text("Current balance", comment: "Label for displaying current wallet balance")
@@ -28,29 +30,46 @@ struct BalanceView: View {
} }
func numericalBalanceView(text: String) -> some View { func numericalBalanceView(text: String) -> some View {
HStack { Group {
Text(verbatim: text) if hide_balance {
.lineLimit(1) Text(verbatim: "*****")
.minimumScaleFactor(0.70) .lineLimit(1)
.font(.veryVeryLargeTitle) .minimumScaleFactor(0.70)
.fontWeight(.heavy) .font(.veryVeryLargeTitle)
.foregroundStyle(PinkGradient)
HStack(alignment: .top) {
Text("SATS", comment: "Abbreviation for Satoshis, smallest bitcoin unit")
.font(.caption)
.fontWeight(.heavy) .fontWeight(.heavy)
.foregroundStyle(PinkGradient) .foregroundStyle(PinkGradient)
} else {
HStack {
Text(verbatim: text)
.lineLimit(1)
.minimumScaleFactor(0.70)
.font(.veryVeryLargeTitle)
.fontWeight(.heavy)
.foregroundStyle(PinkGradient)
HStack(alignment: .top) {
Text("SATS", comment: "Abbreviation for Satoshis, smallest bitcoin unit")
.font(.caption)
.fontWeight(.heavy)
.foregroundStyle(PinkGradient)
}
}
} }
} }
.privacySensitive()
.padding(.bottom) .padding(.bottom)
.onTapGesture {
hide_balance.toggle()
}
} }
} }
struct BalanceView_Previews: PreviewProvider { struct BalanceView_Previews: PreviewProvider {
@State private static var hide_balance: Bool = false
static var previews: some View { static var previews: some View {
BalanceView(balance: 100000000) BalanceView(balance: 100000000, hide_balance: $hide_balance)
BalanceView(balance: nil) BalanceView(balance: nil, hide_balance: $hide_balance)
} }
} }

View File

@@ -138,7 +138,10 @@ struct NWCSettings: View {
Toggle(NSLocalizedString("Disable high balance warning", comment: "Setting to disable high balance warnings on the user's wallet"), isOn: $settings.dismiss_wallet_high_balance_warning) Toggle(NSLocalizedString("Disable high balance warning", comment: "Setting to disable high balance warnings on the user's wallet"), isOn: $settings.dismiss_wallet_high_balance_warning)
.toggleStyle(.switch) .toggleStyle(.switch)
Toggle(NSLocalizedString("Hide balance", comment: "Setting to hide wallet balance."), isOn: $settings.hide_wallet_balance)
.toggleStyle(.switch)
Button(action: { Button(action: {
self.model.disconnect() self.model.disconnect()
dismiss() dismiss()

View File

@@ -8,14 +8,18 @@
import SwiftUI import SwiftUI
struct TransactionView: View { struct TransactionView: View {
@Environment(\.redactionReasons) var redactionReasons
let damus_state: DamusState let damus_state: DamusState
var transaction: WalletConnect.Transaction var transaction: WalletConnect.Transaction
@Binding var hide_balance: Bool
var body: some View { var body: some View {
let redactedForPrivacy = redactionReasons.contains(.privacy)
let isIncomingTransaction = transaction.type == "incoming" let isIncomingTransaction = transaction.type == "incoming"
let txType = isIncomingTransaction ? "arrow-bottom-left" : "arrow-top-right" let txType = isIncomingTransaction ? "arrow-bottom-left" : "arrow-top-right"
let txColor = isIncomingTransaction ? DamusColors.success : Color.gray let txColor = (isIncomingTransaction && !hide_balance && !redactedForPrivacy) ? DamusColors.success : Color.gray
let txOp = isIncomingTransaction ? "+" : "-" let txOp = isIncomingTransaction ? "+" : "-"
let created_at = Date.init(timeIntervalSince1970: TimeInterval(transaction.created_at)) let created_at = Date.init(timeIntervalSince1970: TimeInterval(transaction.created_at))
let formatter = RelativeDateTimeFormatter() let formatter = RelativeDateTimeFormatter()
@@ -26,21 +30,23 @@ struct TransactionView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack(alignment: .center) { HStack(alignment: .center) {
ZStack { ZStack {
ProfilePicView(pubkey: pubkey, size: 45, highlight: .custom(.damusAdaptableBlack, 0.1), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation) ProfilePicView(pubkey: pubkey, size: 45, highlight: .custom(.damusAdaptableBlack, 0.1), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, privacy_sensitive: true)
.onTapGesture { .onTapGesture {
damus_state.nav.push(route: Route.ProfileByKey(pubkey: pubkey)) damus_state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
} }
Image(txType) if !hide_balance && !redactedForPrivacy {
.resizable() Image(txType)
.frame(width: 18, height: 18) .resizable()
.foregroundColor(.white) .frame(width: 18, height: 18)
.padding(2) .foregroundColor(.white)
.background(txColor) .padding(2)
.clipShape(Circle()) .background(txColor)
.overlay(Circle().stroke(Color.damusAdaptableWhite, lineWidth: 1.0)) .clipShape(Circle())
.padding(.top, 25) .overlay(Circle().stroke(Color.damusAdaptableWhite, lineWidth: 1.0))
.padding(.leading, 35) .padding(.top, 25)
.padding(.leading, 35)
}
} }
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
@@ -58,10 +64,17 @@ struct TransactionView: View {
Spacer() Spacer()
Text(verbatim: "\(txOp) \(format_msats(transaction.amount))") if hide_balance {
.font(.headline) Text(verbatim: "*****")
.foregroundColor(txColor) .font(.headline)
.bold() .foregroundColor(txColor)
.bold()
} else {
Text(verbatim: "\(txOp) \(format_msats(transaction.amount))")
.font(.headline)
.foregroundColor(txColor)
.bold()
}
} }
.frame(maxWidth: .infinity, minHeight: 75, alignment: .center) .frame(maxWidth: .infinity, minHeight: 75, alignment: .center)
.padding(.horizontal, 10) .padding(.horizontal, 10)
@@ -107,27 +120,32 @@ struct TransactionsView: View {
transactions?.sorted(by: { $0.created_at > $1.created_at }) transactions?.sorted(by: { $0.created_at > $1.created_at })
} }
@Binding var hide_balance: Bool
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
Text("Latest transactions", comment: "Heading for latest wallet transactions list") Text("Latest transactions", comment: "Heading for latest wallet transactions list")
.foregroundStyle(DamusColors.neutral6) .foregroundStyle(DamusColors.neutral6)
if let sortedTransactions { Group {
if sortedTransactions.isEmpty { if let sortedTransactions {
emptyTransactions if sortedTransactions.isEmpty {
} else { emptyTransactions
ForEach(sortedTransactions, id: \.self) { transaction in } else {
TransactionView(damus_state: damus_state, transaction: transaction) ForEach(sortedTransactions, id: \.self) { transaction in
TransactionView(damus_state: damus_state, transaction: transaction, hide_balance: $hide_balance)
}
} }
} }
else {
// Make sure we do not show "No transactions yet" to the user when still loading (or when failed to load)
// This is important because if we show that when things are not loaded properly, we risk scaring the user into thinking that they have lost funds.
emptyTransactions
.redacted(reason: .placeholder)
.shimmer(true)
}
} }
else { .privacySensitive()
// Make sure we do not show "No transactions yet" to the user when still loading (or when failed to load)
// This is important because if we show that when things are not loaded properly, we risk scaring the user into thinking that they have lost funds.
emptyTransactions
.redacted(reason: .placeholder)
.shimmer(true)
}
} }
} }
@@ -154,8 +172,10 @@ struct TransactionsView_Previews: PreviewProvider {
static let transaction3: WalletConnect.Transaction = WalletConnect.Transaction(type: "outgoing", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "123456789042", amount: 303000, fees_paid: 0, created_at: 1737590101, expires_at: 0, settled_at: 0, metadata: nil) static let transaction3: WalletConnect.Transaction = WalletConnect.Transaction(type: "outgoing", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "123456789042", amount: 303000, fees_paid: 0, created_at: 1737590101, expires_at: 0, settled_at: 0, metadata: nil)
static let transaction4: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "1234567890662", amount: 720000, fees_paid: 0, created_at: 1737090300, expires_at: 0, settled_at: 0, metadata: nil) static let transaction4: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "1234567890662", amount: 720000, fees_paid: 0, created_at: 1737090300, expires_at: 0, settled_at: 0, metadata: nil)
static var test_transactions: [WalletConnect.Transaction] = [transaction1, transaction2, transaction3, transaction4] static var test_transactions: [WalletConnect.Transaction] = [transaction1, transaction2, transaction3, transaction4]
@State private static var hide_balance: Bool = false
static var previews: some View { static var previews: some View {
TransactionsView(damus_state: tds, transactions: test_transactions) TransactionsView(damus_state: tds, transactions: test_transactions, hide_balance: $hide_balance)
} }
} }

View File

@@ -14,7 +14,8 @@ struct WalletView: View {
@State var show_settings: Bool = false @State var show_settings: Bool = false
@ObservedObject var model: WalletModel @ObservedObject var model: WalletModel
@ObservedObject var settings: UserSettingsStore @ObservedObject var settings: UserSettingsStore
@State private var showBalance: Bool = false
init(damus_state: DamusState, model: WalletModel? = nil) { init(damus_state: DamusState, model: WalletModel? = nil) {
self.damus_state = damus_state self.damus_state = damus_state
self._model = ObservedObject(wrappedValue: model ?? damus_state.wallet) self._model = ObservedObject(wrappedValue: model ?? damus_state.wallet)
@@ -47,6 +48,7 @@ struct WalletView: View {
.bold() .bold()
.foregroundStyle(.damusWarningTertiary) .foregroundStyle(.damusWarningTertiary)
} }
.privacySensitive()
.padding() .padding()
.overlay( .overlay(
RoundedRectangle(cornerRadius: 20) RoundedRectangle(cornerRadius: 20)
@@ -56,9 +58,9 @@ struct WalletView: View {
VStack(spacing: 5) { VStack(spacing: 5) {
BalanceView(balance: model.balance) BalanceView(balance: model.balance, hide_balance: $settings.hide_wallet_balance)
TransactionsView(damus_state: damus_state, transactions: model.transactions) TransactionsView(damus_state: damus_state, transactions: model.transactions, hide_balance: $settings.hide_wallet_balance)
} }
} }
.navigationTitle(NSLocalizedString("Wallet", comment: "Navigation title for Wallet view")) .navigationTitle(NSLocalizedString("Wallet", comment: "Navigation title for Wallet view"))