TransactionsView.swift (8973B)
1 // 2 // TransactionsView.swift 3 // damus 4 // 5 // Created by eric on 1/23/25. 6 // 7 8 import SwiftUI 9 10 struct TransactionView: View { 11 @Environment(\.redactionReasons) var redactionReasons 12 13 let damus_state: DamusState 14 var transaction: WalletConnect.Transaction 15 16 @Binding var hide_balance: Bool 17 18 var body: some View { 19 let redactedForPrivacy = redactionReasons.contains(.privacy) 20 let isIncomingTransaction = transaction.type == "incoming" 21 let txType = isIncomingTransaction ? "arrow-bottom-left" : "arrow-top-right" 22 let txColor = (isIncomingTransaction && !hide_balance && !redactedForPrivacy) ? DamusColors.success : Color.gray 23 let txOp = isIncomingTransaction ? "+" : "-" 24 let created_at = Date.init(timeIntervalSince1970: TimeInterval(transaction.created_at)) 25 let formatter = RelativeDateTimeFormatter() 26 let relativeDate = formatter.localizedString(for: created_at, relativeTo: Date.now) 27 let event = decode_nostr_event_json(transaction.description ?? "") ?? transaction.metadata?.nostr 28 let pubkey = self.pubkeyToDisplay(for: event, isIncomingTransaction: isIncomingTransaction) 29 30 VStack(alignment: .leading) { 31 HStack(alignment: .center) { 32 ZStack { 33 ProfilePicView(pubkey: pubkey ?? ANON_PUBKEY, size: 45, highlight: .custom(.damusAdaptableBlack, 0.1), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation, privacy_sensitive: true) 34 .onTapGesture { 35 if let pubkey { 36 damus_state.nav.push(route: Route.ProfileByKey(pubkey: pubkey)) 37 } 38 } 39 40 if !hide_balance && !redactedForPrivacy { 41 Image(txType) 42 .resizable() 43 .frame(width: 18, height: 18) 44 .foregroundColor(.white) 45 .padding(2) 46 .background(txColor) 47 .clipShape(Circle()) 48 .overlay(Circle().stroke(Color.damusAdaptableWhite, lineWidth: 1.0)) 49 .padding(.top, 25) 50 .padding(.leading, 35) 51 } 52 } 53 54 VStack(alignment: .leading, spacing: 10) { 55 56 Text(self.userDisplayName(pubkey: pubkey)) 57 .font(.headline) 58 .bold() 59 .foregroundColor(DamusColors.adaptableBlack) 60 61 Text(relativeDate) 62 .font(.caption) 63 .foregroundColor(Color.gray) 64 } 65 .padding(.horizontal, 10) 66 67 Spacer() 68 69 if hide_balance { 70 Text(verbatim: "*****") 71 .font(.headline) 72 .foregroundColor(txColor) 73 .bold() 74 } else { 75 Text(verbatim: "\(txOp) \(format_msats(transaction.amount))") 76 .font(.headline) 77 .foregroundColor(txColor) 78 .bold() 79 } 80 } 81 .frame(maxWidth: .infinity, minHeight: 75, alignment: .center) 82 .padding(.horizontal, 10) 83 .background(DamusColors.neutral1) 84 .cornerRadius(10) 85 .overlay( 86 RoundedRectangle(cornerRadius: 10) 87 .stroke(DamusColors.neutral3, lineWidth: 1) 88 ) 89 } 90 } 91 92 func pubkeyToDisplay(for zapRequest: NostrEvent?, isIncomingTransaction: Bool) -> Pubkey? { 93 guard let zapRequest else { return nil } 94 if isIncomingTransaction { 95 return zapRequest.pubkey // We want to know who sent it to us 96 } 97 else { 98 return zapRequest.referenced_pubkeys.first // We want to know who we sent it to 99 } 100 } 101 102 func userDisplayName(pubkey: Pubkey?) -> String { 103 guard let pubkey else { 104 return NSLocalizedString("Unknown", comment: "A name label for an unknown user") 105 } 106 107 let profile_txn = damus_state.profiles.lookup(id: pubkey, txn_name: "txview-profile") 108 let profile = profile_txn?.unsafeUnownedValue 109 110 return Profile.displayName(profile: profile, pubkey: pubkey).displayName 111 } 112 113 } 114 115 struct TransactionsView: View { 116 117 let damus_state: DamusState 118 let transactions: [WalletConnect.Transaction]? 119 var sortedTransactions: [WalletConnect.Transaction]? { 120 transactions?.sorted(by: { $0.created_at > $1.created_at }) 121 } 122 123 @Binding var hide_balance: Bool 124 125 var body: some View { 126 VStack(alignment: .leading, spacing: 10) { 127 Text("Latest transactions", comment: "Heading for latest wallet transactions list") 128 .foregroundStyle(DamusColors.neutral6) 129 130 Group { 131 if let sortedTransactions { 132 if sortedTransactions.isEmpty { 133 emptyTransactions 134 } else { 135 ForEach(sortedTransactions, id: \.self) { transaction in 136 TransactionView(damus_state: damus_state, transaction: transaction, hide_balance: $hide_balance) 137 } 138 } 139 } 140 else { 141 // Make sure we do not show "No transactions yet" to the user when still loading (or when failed to load) 142 // 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. 143 emptyTransactions 144 .redacted(reason: .placeholder) 145 .shimmer(true) 146 } 147 } 148 .privacySensitive() 149 } 150 } 151 152 var emptyTransactions: some View { 153 HStack { 154 Text("No transactions yet", comment: "Message shown when no transactions are available") 155 .foregroundStyle(DamusColors.neutral6) 156 } 157 .frame(maxWidth: .infinity, minHeight: 75, alignment: .center) 158 .padding(.horizontal, 10) 159 .background(DamusColors.neutral1) 160 .cornerRadius(10) 161 .overlay( 162 RoundedRectangle(cornerRadius: 10) 163 .stroke(DamusColors.neutral3, lineWidth: 1) 164 ) 165 } 166 } 167 168 struct TransactionsView_Previews: PreviewProvider { 169 static let tds = test_damus_state 170 static let transaction1: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "{\"id\":\"7c0999a5870ca3ba0186a29a8650152b555cee29b53b5b8747d8a3798042d01c\",\"pubkey\":\"b8851a06dfd79d48fc325234a15e9a46a32a0982a823b54cdf82514b9b120ba1\",\"created_at\":1736383715,\"kind\":9734,\"tags\":[[\"p\",\"520830c334a3f79f88cac934580d26f91a7832c6b21fb9625690ea2ed81b5626\"],[\"amount\",\"21000\"],[\"e\",\"a25e152a4cd1b3bbc3d22e8e9315d8ea1f35c227b2f212c7cff18abff36fa208\"],[\"relays\",\"wss://nos.lol\",\"wss://nostr.wine\",\"wss://premium.primal.net\",\"wss://relay.damus.io\",\"wss://relay.nostr.band\",\"wss://relay.nostrarabia.com\"]],\"content\":\"🫡 Onward!\",\"sig\":\"e77d16822fa21b9c2e6b580b51c470588052c14aeb222f08f0e735027e366157c8742a6d5cb850780c2bf44ac63d89b048e5cc56dd47a1bfc740a3173e578f4e\"}", description_hash: "", preimage: "", payment_hash: "1234567890", amount: 21000, fees_paid: 0, created_at: 1737736866, expires_at: 0, settled_at: 0, metadata: nil) 171 static let transaction2: WalletConnect.Transaction = WalletConnect.Transaction(type: "incoming", invoice: "", description: "", description_hash: "", preimage: "", payment_hash: "123456789033", amount: 100000000, fees_paid: 0, created_at: 1737690090, expires_at: 0, settled_at: 0, metadata: nil) 172 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) 173 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) 174 static var test_transactions: [WalletConnect.Transaction] = [transaction1, transaction2, transaction3, transaction4] 175 176 @State private static var hide_balance: Bool = false 177 178 static var previews: some View { 179 TransactionsView(damus_state: tds, transactions: test_transactions, hide_balance: $hide_balance) 180 } 181 }