summaryrefslogtreecommitdiff
path: root/TalerWallet1/Views/Transactions/TransactionRowView.swift
blob: dea13310b30cfc6c019b8467b80c5811734ef791 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/*
 * This file is part of GNU Taler, ©2022-23 Taler Systems S.A.
 * See LICENSE.md
 */
import SwiftUI
import taler_swift

struct IconBadge: View {
    let foreColor:Color
    let done: Bool
//    let pending: Bool
    let incoming: Bool
    let shouldConfirm: Bool
    let needsKYC: Bool

    var body: some View {
        let imageName = incoming && done ? DONE_INCOMING
                              : incoming ? PENDING_INCOMING
                // since the money already left the wallet, show DONE
                                         : DONE_OUTGOING        // PENDING_OUTGOING
        let badgeName = needsKYC ? NEEDS_KYC
                                 : CONFIRM_BANK
        HStack(alignment: .top, spacing: -8) {      // TODO: adapt spacing to dynamic fontsize
            ZStack {
                // "plus.diamond" is wider than "plus.circle.fill"
                // ZStack centers the main icon, and the badge will always be at the same position
                Image(systemName: PENDING_INCOMING)
                    .foregroundColor(.clear)
                Image(systemName: imageName)
                    .foregroundColor(foreColor)
            }.talerFont(.largeTitle)
            Image(systemName: badgeName)
                .talerFont(.caption)
                .foregroundColor((needsKYC || shouldConfirm) ? .red : .clear)
        }.accessibilityHidden(true)
    }
}

struct TransactionRowView: View {
    let transaction : Transaction

    @Environment(\.sizeCategory) var sizeCategory
    @Environment(\.colorScheme) private var colorScheme
    @Environment(\.colorSchemeContrast) private var colorSchemeContrast

    func needVStack(available: CGFloat, contentWidth: CGFloat, valueWidth: CGFloat) -> Bool {
        available < (contentWidth + valueWidth + 40)
    }

    func topString() -> String {
        switch transaction {
            case .payment(let paymentTransaction):
                return paymentTransaction.details.info.merchant.name
            default:
                return transaction.isDone ? transaction.localizedTypePast
                                          : transaction.localizedType
        }
    }

    var body: some View {
        let common = transaction.common
        let pending = transaction.isPending
        let needsKYC = transaction.isPendingKYC
        let shouldConfirm = transaction.shouldConfirm
        let done = transaction.isDone
        let doneOrPending = done || pending
        let increasedContrast = colorSchemeContrast == .increased
        let details = transaction.detailsToShow()
        let keys = details.keys

        let (dateString, date) = TalerDater.dateString(from: common.timestamp, relative: true)
        let incoming = common.incoming()
        let foreColor = pending ? WalletColors().pendingColor(incoming)
                      : done ? WalletColors().transactionColor(incoming)
                             : WalletColors().uncompletedColor
        let textColor = doneOrPending ? .primary
               : colorScheme == .dark ? .secondary
                  : increasedContrast ? Color(.darkGray)
                                      : .secondary  // Color(.tertiaryLabel)

        let iconBadge = IconBadge(foreColor: foreColor, done: done, incoming: incoming,
                                  shouldConfirm: shouldConfirm, needsKYC: needsKYC)
        let amountV = AmountV(common.amountEffective)
            .foregroundColor(foreColor)

        let topString = topString()
        let centerTop = Text(topString)
            .foregroundColor(textColor)
            .strikethrough(!doneOrPending, color: .red)
            .talerFont(.headline)
//            .fontWeight(.medium)      iOS 16
            .padding(.bottom, -2.0)
            .accessibilityLabel(doneOrPending ? topString : topString +  ", canceled")
        let centerBottom = Text(dateString)
            .foregroundColor(textColor)
            .talerFont(.callout)

#if DEBUG
        let debug = false
        let red = debug ? Color.red : Color.clear
        let green = debug ? Color.green : Color.clear
        let blue = debug ? Color.blue : Color.clear
        let orange = debug ? Color.orange : Color.clear
#endif

        let layout1 = HStack(spacing: 0) {
            HStack(spacing: -4) {
                iconBadge
//                Spacer(minLength: 0)
                VStack(alignment: .leading) {
                    centerTop
                    centerBottom
                }
                Spacer(minLength: 2)
            }
            amountV //.frame(maxWidth: .infinity, alignment: .trailing)
        }

        let layout2 = VStack(spacing: 0) {
            centerTop
            HStack(spacing: 8) {
                HStack(spacing: 0) {
                    iconBadge
#if DEBUG
                        .border(blue)
#endif
                    Spacer(minLength: 0)
                    centerBottom
                    Spacer(minLength: 2)
                }
#if DEBUG
                    .border(green)
#endif
                amountV
#if DEBUG
                    .border(red)
#endif
            }
        }

        let layout3 = VStack(spacing: 0) {
            HStack(spacing: 8) {
                HStack(spacing: 0) {
                    iconBadge
                    Spacer(minLength: 0)
                    centerTop
                    Spacer(minLength: 2)
                }
                amountV
            }
            centerBottom
        }

        let layout4 = VStack(spacing: 0) {
            centerTop
            HStack(spacing: -4) {
                iconBadge
#if DEBUG
                    .border(green)
#endif
                Spacer(minLength: 2)
                amountV
#if DEBUG
                    .border(green)
#endif
            }
#if DEBUG
                .border(orange)
#endif
            centerBottom
        }

        Group {
            if #available(iOS 16.0, *) {
                ViewThatFits(in: .horizontal) {
                    layout1
#if DEBUG
                        .border(green)
#endif
                    layout2
#if DEBUG
                        .border(orange)
#endif
                    layout3
#if DEBUG
                        .border(red)
#endif
                    layout4
#if DEBUG
                        .border(blue)
#endif
                }
            } else { layout4 } // view for iOS 15
        }
            .accessibilityElement(children: .combine)
            .accessibilityValue(needsKYC ? ". Needs K Y C" :
                           shouldConfirm ? ". Needs bank confirmation" : EMPTYSTRING)
            .accessibilityHint("Will go to detail view.")
    }
}
// MARK: -
#if DEBUG
struct TransactionRow_Previews: PreviewProvider {
    static var withdrawal = Transaction(incoming: true,
                                         pending: false,
                                              id: "some withdrawal ID",
                                            time: Timestamp(from: 1_666_000_000_000))
    static var payment = Transaction(incoming: false,
                                      pending: false,
                                           id: "some payment ID",
                                         time: Timestamp(from: 1_666_666_000_000))
    static var previews: some View {
        List {
            TransactionRowView(transaction: withdrawal)
            TransactionRowView(transaction: payment)
        }
    }
}
// MARK: -
extension Transaction {             // for PreViews
    init(incoming: Bool, pending: Bool, id: String, time: Timestamp) {
        let effective = incoming ? 480 : 520
        let common = TransactionCommon(type: incoming ? .withdrawal : .payment,
                                    txState: TransactionState(major: pending ? TransactionMajorState.pending
                                                                             : TransactionMajorState.done),
                            amountEffective: Amount(currency: LONGCURRENCY, cent: UInt64(effective)),
                                  amountRaw: Amount(currency: LONGCURRENCY, cent: 5),
                              transactionId: id,
                                  timestamp: time,
                                  txActions: [.abort])
        if incoming {
            // if pending then manual else bank-integrated
            let payto = "payto://iban/SANDBOXX/DE159593?receiver-name=Exchange+Company&amount=KUDOS%3A9.99&message=Taler+Withdrawal+J41FQPJGAP1BED1SFSXHC989EN8HRDYAHK688MQ228H6SKBMV0AG"
            let withdrawalDetails = WithdrawalDetails(type: pending ? WithdrawalDetails.WithdrawalType.manual
                                                                    : WithdrawalDetails.WithdrawalType.bankIntegrated,
                                                reservePub: "PuBlIc_KeY_oF_tHe_ReSeRvE",
                                            reserveIsReady: false,
                                                 confirmed: false)
            let wDetails = WithdrawalTransactionDetails(exchangeBaseUrl: DEMOEXCHANGE,
                                                      withdrawalDetails: withdrawalDetails)
            self = .withdrawal(WithdrawalTransaction(common: common, details: wDetails))
        } else {
            let merchant = Merchant(name: "some random shop")
            let info = OrderShortInfo(orderId: "some order ID",
                                     merchant: merchant,
                                      summary: "some product summary",
                                     products: [])
            let pDetails = PaymentTransactionDetails(info: info,
                                               proposalId: "some proposal ID",
                                           totalRefundRaw: Amount(currency: LONGCURRENCY, cent: 300),
                                     totalRefundEffective: Amount(currency: LONGCURRENCY, cent: 280),
                                                  refunds: [],
                                        refundQueryActive: false)
            self = .payment(PaymentTransaction(common: common, details: pDetails))
        }
    }
}
#endif