taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

ManualDetailsWireV.swift (15675B)


      1 /*
      2  * This file is part of GNU Taler, ©2022-26 Taler Systems S.A.
      3  * See LICENSE.md
      4  */
      5 /**
      6  * @author Marc Stibane
      7  */
      8 import SwiftUI
      9 import OrderedCollections
     10 import taler_swift
     11 
     12 struct TransferRestrictionsV: View {
     13     let amountStr: (String, String)
     14     let obtainStr: (String, String)?
     15     let debitIBAN: String?
     16     let restrictions: [AccountRestriction]?
     17 
     18     @AppStorage("minimalistic") var minimalistic: Bool = false
     19 
     20     @State private var selectedLanguage = Locale.preferredLanguageCode
     21 
     22     private func transferMini(_ amountS: String) -> String {
     23         let amountNBS = amountS.nbs
     24         return String(localized: "Transfer \(amountNBS) to the payment service.")
     25     }
     26     private func transferMaxi(_ amountS: String, _ obtainS: String) -> String {
     27         let amountNBS = amountS.nbs
     28         let obtainNBS = obtainS.nbs
     29         return String(localized: "You need to transfer \(amountNBS) from your regular bank account to the payment service to receive \(obtainNBS) as digital cash in this wallet.")
     30     }
     31 
     32     private func authMini(_ amountS: String, _ debitS: String) -> String {
     33         let amountNBS = amountS.nbs
     34         return String(localized: "Transfer \(amountNBS) from account \(debitS) to verify having control over it.")
     35     }
     36     private func authMaxi(_ amountS: String, _ debitS: String) -> String {
     37         let amountNBS = amountS.nbs
     38         return String(localized: "You need to transfer \(amountNBS) to the payment service from your bank account \(debitS) to verify having control over it. Don't use a different bank account, or the verification will fail.")
     39     }
     40 
     41     var body: some View {
     42         VStack(alignment: .leading) {
     43             if let obtainStr {
     44                 Text(minimalistic ? transferMini(amountStr.0)
     45                                   : transferMaxi(amountStr.0, obtainStr.0))
     46                     .accessibilityLabel(minimalistic ? transferMini(amountStr.1)
     47                                                      : transferMaxi(amountStr.1, obtainStr.1))
     48                     .talerFont(.body)
     49                     .multilineTextAlignment(.leading)
     50             } else if let debitIBAN {
     51                 Text(minimalistic ? authMini(amountStr.0, debitIBAN)
     52                                   : authMaxi(amountStr.0, debitIBAN))
     53                     .accessibilityLabel(minimalistic ? authMini(amountStr.1, debitIBAN)
     54                                                      : authMaxi(amountStr.1, debitIBAN))
     55                     .talerFont(.body)
     56                     .multilineTextAlignment(.leading)
     57             }
     58             if let restrictions {
     59                 ForEach(restrictions) { restriction in
     60                     let hintsI18n = restriction.human_hint_i18n
     61                     if let hintsI18n, !hintsI18n.isEmpty {
     62 //                        let sortedDict = OrderedDictionary(uniqueKeys: hintsI18n.keys, values: hintsI18n.values)
     63 //                        var sorted: OrderedDictionary<String:String>
     64                         let sortedDict = OrderedDictionary(uncheckedUniqueKeysWithValues: hintsI18n.sorted { $0.key < $1.key })
     65                         Picker("Restriction:", selection: $selectedLanguage) {
     66                             ForEach(sortedDict.keys, id: \.self) {
     67                                 Text(sortedDict[$0] ?? "missing hint")
     68                             }
     69                         }
     70                         .padding(.top)
     71                     } else if let hint = restriction.human_hint {
     72                         let mark = Image(systemName: EXCLAMATION)
     73                         Text("\(mark) \(hint)")     // verbatim: doesn't work here, will not show the image. Thus we must set this to "Don't translate"
     74                             .padding(.top)
     75                     }
     76                 }
     77             }
     78         }
     79     }
     80 }
     81 // MARK: -
     82 struct ManualDetailsWireV: View {
     83     let stack: CallStack
     84     let reservePub: String
     85     let receiverStr: String
     86     let receiverZip: String?
     87     let receiverTown: String?
     88     let iban: String?                   // TODO: BBAN
     89     let cyclos: String
     90     let xTaler: String
     91     let amountValue: String             // string representation of the value, formatted as "`integer`.`fraction`"
     92     let amountStr: (String, String)
     93     let obtainStr: (String, String)?    // only for withdrawal
     94     let debitIBAN: String?              // only for deposit auth
     95     let account: ExchangeAccountDetails
     96 
     97     @AppStorage("minimalistic") var minimalistic: Bool = false
     98     let navTitle = String(localized: "Wire transfer", comment: "ViewTitle of wire-transfer instructions")
     99 
    100     private func step3(_ amountS: String) -> String {
    101         let amountNBS = amountS.nbs
    102         let bePatient = String(localized: "Depending on your bank the transfer can take from minutes to two working days, please be patient.")
    103         if let debitIBAN {
    104             return minimalistic ? String(localized: "Transfer \(amountNBS) from \(debitIBAN).")
    105                                 : String(localized: "Finish the wire transfer of \(amountNBS) in your banking app or website to verify your bank account \(debitIBAN).") + "\n" + bePatient
    106         }
    107         return minimalistic ? String(localized: "Transfer \(amountNBS).")
    108                             : String(localized: "Finish the wire transfer of \(amountNBS) in your banking app or website, then this withdrawal will proceed automatically.") + "\n" + bePatient
    109     }
    110 
    111     @ViewBuilder func payeeZip() -> some View {
    112         HStack {
    113             VStack(alignment: .leading) {
    114                 Text("Zip code:")
    115                     .talerFont(.subheadline)
    116                 Text(receiverZip ?? EMPTYSTRING)
    117                     .monospacedDigit()
    118                     .padding(.leading)
    119             }   .frame(maxWidth: .infinity, alignment: .leading)
    120                 .accessibilityElement(children: .combine)
    121                 .accessibilityLabel(Text("Zip code", comment: "a11y"))
    122             CopyButton(textToCopy: receiverZip ?? EMPTYSTRING, vertical: true)
    123                 .accessibilityLabel(Text("Copy the zip code", comment: "a11y"))
    124                 .disabled(false)
    125         }   .padding(.top, -8)
    126     }
    127 
    128     @ViewBuilder func payeeTown() -> some View {
    129         HStack {
    130             VStack(alignment: .leading) {
    131                 Text("City:")
    132                     .talerFont(.subheadline)
    133                 Text(receiverTown ?? EMPTYSTRING)
    134                     .monospacedDigit()
    135                     .padding(.leading)
    136             }   .frame(maxWidth: .infinity, alignment: .leading)
    137                 .accessibilityElement(children: .combine)
    138                 .accessibilityLabel(Text("City", comment: "a11y"))
    139             CopyButton(textToCopy: receiverTown ?? EMPTYSTRING, vertical: true)
    140                 .accessibilityLabel(Text("Copy the city", comment: "a11y"))
    141                 .disabled(false)
    142         }   .padding(.top, -8)
    143     }
    144 
    145     var body: some View {
    146         List {
    147             let cryptoString = debitIBAN == nil ? reservePub : "kyc" + reservePub
    148             let cryptocode = HStack {
    149                 Text(cryptoString)
    150                     .monospacedDigit()
    151                     .accessibilityLabel(Text("Cryptocode", comment: "a11y"))
    152                     .frame(maxWidth: .infinity, alignment: .leading)
    153                 CopyButton(textToCopy: cryptoString, vertical: true)
    154                     .accessibilityLabel(Text("Copy the cryptocode", comment: "a11y"))
    155                     .disabled(false)
    156             }   .padding(.leading)
    157             let payeeCode = HStack {
    158                 VStack(alignment: .leading) {
    159                     Text("Recipient:")
    160                         .talerFont(.subheadline)
    161                     Text(receiverStr)
    162                         .monospacedDigit()
    163                         .padding(.leading)
    164                 }   .frame(maxWidth: .infinity, alignment: .leading)
    165                     .accessibilityElement(children: .combine)
    166                     .accessibilityLabel(Text("Recipient", comment: "a11y"))
    167                 CopyButton(textToCopy: receiverStr, vertical: true)
    168 //                CopyButton(textToCopy: buildReceiver(), vertical: true)
    169                     .accessibilityLabel(Text("Copy the recipient", comment: "a11y"))
    170                     .disabled(false)
    171             }   .padding(.top, -8)
    172             let ibanCode = HStack {
    173                 VStack(alignment: .leading) {
    174                     Text("IBAN:")                   // TODO: BBAN
    175                         .talerFont(.subheadline)
    176                     Text(iban ?? EMPTYSTRING)
    177                         .monospacedDigit()
    178                         .padding(.leading)
    179                 }   .frame(maxWidth: .infinity, alignment: .leading)
    180                     .accessibilityElement(children: .combine)
    181                     .accessibilityLabel(Text("IBAN of the recipient", comment: "a11y")) // TODO: BBAN
    182                 CopyButton(textToCopy: iban ?? EMPTYSTRING, vertical: true)
    183                     .accessibilityLabel(Text("Copy the IBAN", comment: "a11y"))         // TODO: BBAN
    184                     .disabled(false)
    185             } //  .padding(.top, -8)
    186             let amountCode = HStack {
    187                 VStack(alignment: .leading) {
    188                     Text("Amount:")
    189                         .talerFont(.subheadline)
    190                     Text(amountStr.0)
    191                         .accessibilityLabel(amountStr.1)
    192                         .monospacedDigit()
    193                         .padding(.leading)
    194                 }   .frame(maxWidth: .infinity, alignment: .leading)
    195                     .accessibilityElement(children: .combine)
    196                     .accessibilityLabel(Text("Amount to transfer", comment: "a11y"))
    197                 CopyButton(textToCopy: amountValue, vertical: true)             // only digits + separator, no currency name or symbol
    198                     .accessibilityLabel(Text("Copy the amount", comment: "a11y"))
    199                     .disabled(false)
    200             }   .padding(.top, -8)
    201             let xTalerCode = HStack {
    202                 VStack(alignment: .leading) {
    203                     Text("Account:")
    204                         .talerFont(.subheadline)
    205                     Text(xTaler)
    206                         .monospacedDigit()
    207                         .padding(.leading)
    208                 }   .frame(maxWidth: .infinity, alignment: .leading)
    209                     .accessibilityElement(children: .combine)
    210                     .accessibilityLabel(Text("account of the recipient", comment: "a11y"))
    211                 CopyButton(textToCopy: xTaler, vertical: true)
    212                     .accessibilityLabel(Text("Copy the account", comment: "a11y"))
    213                     .disabled(false)
    214             }   .padding(.top, -8)
    215             let step1 = Text(minimalistic ? "**Step 1:** Copy+Paste this subject:"
    216                              : "**Step 1:** Copy this code and paste it into the subject/purpose field in your banking app or bank website:")
    217                 .talerFont(.body)
    218                 .multilineTextAlignment(.leading)
    219             let warningIcon = Image(systemName: WARNING)
    220             let manda1 = String(localized: "This is mandatory, otherwise your money will not arrive in this wallet.")
    221             let manda2 = String(localized: "This is mandatory, otherwise the verification will fail.")
    222             let mandatory = Text("\(warningIcon) " + (debitIBAN == nil ? manda1 : manda2))
    223                 .bold()
    224                 .talerFont(.body)
    225                 .multilineTextAlignment(.leading)
    226                 .listRowSeparator(.hidden)
    227             let step2i = Text(minimalistic ? "**Step 2:** Copy+Paste recipient and IBAN:"
    228                               : "**Step 2:** If you don't already have it in your banking favorites list, then copy and paste recipient and IBAN into the recipient/IBAN fields in your banking app or website (and save it as favorite for the next time):")       // TODO: BBAN
    229                 .talerFont(.body)
    230                 .multilineTextAlignment(.leading)
    231                 .padding(.top)
    232             let step2x = Text(minimalistic ? "**Step 2:** Copy+Paste recipient and account:"
    233                               : "**Step 2:** Copy and paste recipient and account into the corresponding fields in your banking app or website:")
    234                 .talerFont(.body)
    235                 .multilineTextAlignment(.leading)
    236                 .padding(.top)
    237             let step3A11y = String(localized: "Step 3: \(step3(amountStr.1))", comment: "a11y")
    238             let step3Head: LocalizedStringKey = "**Step 3:** \(step3(amountStr.0))"
    239             let step3 = Text(step3Head)
    240                 .accessibilityLabel(step3A11y)
    241                 .talerFont(.body)
    242                 .multilineTextAlignment(.leading)
    243 
    244             Group {
    245                 TransferRestrictionsV(amountStr: amountStr,
    246                                       obtainStr: obtainStr,
    247                                       debitIBAN: debitIBAN,
    248                                    restrictions: account.creditRestrictions)
    249                 .listRowSeparator(.visible)
    250                 step1
    251                 if !minimalistic {
    252                     mandatory
    253                 }
    254                 cryptocode
    255                 if iban != nil {
    256                     step2i
    257                     payeeCode
    258                     if let receiverZip {
    259                         if !receiverZip.isEmpty {
    260                             payeeZip()
    261                         }
    262                     }
    263                     if let receiverTown {
    264                         if !receiverTown.isEmpty {
    265                             payeeTown()
    266                         }
    267                     }
    268                     ibanCode
    269                 } else if cyclos.count > 0 {
    270                     step2x
    271                     payeeCode
    272                 } else {
    273                     step2x
    274                     payeeCode
    275                     xTalerCode
    276                 }
    277                 amountCode
    278 //                    .padding(.top)
    279                 step3 // .padding(.top, 6)
    280             }.listRowSeparator(.hidden)
    281         }
    282         .navigationTitle(navTitle)
    283         .onAppear() {
    284 //            symLog.log("onAppear")
    285             DebugViewC.shared.setViewID(VIEW_WITHDRAW_INSTRUCTIONS, stack: stack.push())
    286         }
    287     }
    288 }
    289 
    290 // MARK: -
    291 #if DEBUG
    292 //struct ManualDetailsWire_Previews: PreviewProvider {
    293 //    static var previews: some View {
    294 //        let common = TransactionCommon(type: .withdrawal,
    295 //                              transactionId: "someTxID",
    296 //                                  timestamp: Timestamp(from: 1_666_666_000_000),
    297 //                                    txState: TransactionState(major: .done),
    298 //                                  txActions: [])
    299 //                            amountEffective: Amount(currency: LONGCURRENCY, cent: 110),
    300 //                                  amountRaw: Amount(currency: LONGCURRENCY, cent: 220),
    301 //        let payto = "payto://iban/SANDBOXX/DE159593?receiver-name=Exchange+Company"
    302 //        let details = WithdrawalDetails(type: .manual,
    303 //                                  reservePub: "ReSeRvEpUbLiC_KeY_FoR_WiThDrAwAl",
    304 //                              reserveIsReady: false,
    305 //                                   confirmed: false)
    306 //        List {
    307 //            ManualDetailsWireV(stack: CallStack("Preview"),
    308 //                             details: details,
    309 //                         receiverStr: <#T##String#>,
    310 //                                iban: <#T##String?#>,
    311 //                              xTaler: <#T##String#>,
    312 //                           amountStr: <#T##String#>,
    313 //                           obtainStr: <#T##String#>,
    314 //                             account: T##ExchangeAccountDetails)
    315 //        }
    316 //    }
    317 //}
    318 #endif