damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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 }