taler-ios

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

ManualDetailsWireV.swift (15032B)


      1 /*
      2  * This file is part of GNU Taler, ©2022-25 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                     if let hintsI18n = restriction.human_hint_i18n {
     61 //                        let sortedDict = OrderedDictionary(uniqueKeys: hintsI18n.keys, values: hintsI18n.values)
     62 //                        var sorted: OrderedDictionary<String:String>
     63                         let sortedDict = OrderedDictionary(uncheckedUniqueKeysWithValues: hintsI18n.sorted { $0.key < $1.key })
     64                         Picker("Restriction:", selection: $selectedLanguage) {
     65                             ForEach(sortedDict.keys, id: \.self) {
     66                                 Text(sortedDict[$0] ?? "missing hint")
     67                             }
     68                         }
     69                     } else if let hint = restriction.human_hint {
     70                         Text(hint)
     71                     }
     72                 }
     73             }
     74         }
     75     }
     76 }
     77 // MARK: -
     78 struct ManualDetailsWireV: View {
     79     let stack: CallStack
     80     let reservePub: String
     81     let receiverStr: String
     82     let receiverZip: String?
     83     let receiverTown: String?
     84     let iban: String?
     85     let xTaler: String
     86     let amountValue: String             // string representation of the value, formatted as "`integer`.`fraction`"
     87     let amountStr: (String, String)
     88     let obtainStr: (String, String)?    // only for withdrawal
     89     let debitIBAN: String?              // only for deposit auth
     90     let account: ExchangeAccountDetails
     91 
     92     @AppStorage("minimalistic") var minimalistic: Bool = false
     93     let navTitle = String(localized: "Wire transfer", comment: "ViewTitle of wire-transfer instructions")
     94 
     95     private func step3(_ amountS: String) -> String {
     96         let amountNBS = amountS.nbs
     97         let bePatient = String(localized: "Depending on your bank the transfer can take from minutes to two working days, please be patient.")
     98         if let debitIBAN {
     99             return minimalistic ? String(localized: "Transfer \(amountNBS) from \(debitIBAN).")
    100                                 : String(localized: "Finish the wire transfer of \(amountNBS) in your banking app or website to verify your bank account \(debitIBAN).") + "\n" + bePatient
    101         }
    102         return minimalistic ? String(localized: "Transfer \(amountNBS).")
    103                             : String(localized: "Finish the wire transfer of \(amountNBS) in your banking app or website, then this withdrawal will proceed automatically.") + "\n" + bePatient
    104     }
    105 
    106     @ViewBuilder func payeeZip() -> some View {
    107         HStack {
    108             VStack(alignment: .leading) {
    109                 Text("Zip code:")
    110                     .talerFont(.subheadline)
    111                 Text(receiverZip ?? EMPTYSTRING)
    112                     .monospacedDigit()
    113                     .padding(.leading)
    114             }   .frame(maxWidth: .infinity, alignment: .leading)
    115                 .accessibilityElement(children: .combine)
    116                 .accessibilityLabel(Text("Zip code", comment: "a11y"))
    117             CopyButton(textToCopy: receiverZip ?? EMPTYSTRING, vertical: true)
    118                 .accessibilityLabel(Text("Copy the zip code", comment: "a11y"))
    119                 .disabled(false)
    120         }   .padding(.top, -8)
    121     }
    122 
    123     @ViewBuilder func payeeTown() -> some View {
    124         HStack {
    125             VStack(alignment: .leading) {
    126                 Text("City:")
    127                     .talerFont(.subheadline)
    128                 Text(receiverTown ?? EMPTYSTRING)
    129                     .monospacedDigit()
    130                     .padding(.leading)
    131             }   .frame(maxWidth: .infinity, alignment: .leading)
    132                 .accessibilityElement(children: .combine)
    133                 .accessibilityLabel(Text("City", comment: "a11y"))
    134             CopyButton(textToCopy: receiverTown ?? EMPTYSTRING, vertical: true)
    135                 .accessibilityLabel(Text("Copy the city", comment: "a11y"))
    136                 .disabled(false)
    137         }   .padding(.top, -8)
    138     }
    139 
    140     var body: some View {
    141         List {
    142             let cryptoString = debitIBAN == nil ? reservePub : "kyc" + reservePub
    143             let cryptocode = HStack {
    144                 Text(cryptoString)
    145                     .monospacedDigit()
    146                     .accessibilityLabel(Text("Cryptocode", comment: "a11y"))
    147                     .frame(maxWidth: .infinity, alignment: .leading)
    148                 CopyButton(textToCopy: cryptoString, vertical: true)
    149                     .accessibilityLabel(Text("Copy the cryptocode", comment: "a11y"))
    150                     .disabled(false)
    151             }   .padding(.leading)
    152             let payeeCode = HStack {
    153                 VStack(alignment: .leading) {
    154                     Text("Recipient:")
    155                         .talerFont(.subheadline)
    156                     Text(receiverStr)
    157                         .monospacedDigit()
    158                         .padding(.leading)
    159                 }   .frame(maxWidth: .infinity, alignment: .leading)
    160                     .accessibilityElement(children: .combine)
    161                     .accessibilityLabel(Text("Recipient", comment: "a11y"))
    162                 CopyButton(textToCopy: receiverStr, vertical: true)
    163 //                CopyButton(textToCopy: buildReceiver(), vertical: true)
    164                     .accessibilityLabel(Text("Copy the recipient", comment: "a11y"))
    165                     .disabled(false)
    166             }   .padding(.top, -8)
    167             let ibanCode = HStack {
    168                 VStack(alignment: .leading) {
    169                     Text("IBAN:")
    170                         .talerFont(.subheadline)
    171                     Text(iban ?? EMPTYSTRING)
    172                         .monospacedDigit()
    173                         .padding(.leading)
    174                 }   .frame(maxWidth: .infinity, alignment: .leading)
    175                     .accessibilityElement(children: .combine)
    176                     .accessibilityLabel(Text("IBAN of the recipient", comment: "a11y"))
    177                 CopyButton(textToCopy: iban ?? EMPTYSTRING, vertical: true)
    178                     .accessibilityLabel(Text("Copy the IBAN", comment: "a11y"))
    179                     .disabled(false)
    180             } //  .padding(.top, -8)
    181             let amountCode = HStack {
    182                 VStack(alignment: .leading) {
    183                     Text("Amount:")
    184                         .talerFont(.subheadline)
    185                     Text(amountStr.0)
    186                         .accessibilityLabel(amountStr.1)
    187                         .monospacedDigit()
    188                         .padding(.leading)
    189                 }   .frame(maxWidth: .infinity, alignment: .leading)
    190                     .accessibilityElement(children: .combine)
    191                     .accessibilityLabel(Text("Amount to transfer", comment: "a11y"))
    192                 CopyButton(textToCopy: amountValue, vertical: true)             // only digits + separator, no currency name or symbol
    193                     .accessibilityLabel(Text("Copy the amount", comment: "a11y"))
    194                     .disabled(false)
    195             }   .padding(.top, -8)
    196             let xTalerCode = HStack {
    197                 VStack(alignment: .leading) {
    198                     Text("Account:")
    199                         .talerFont(.subheadline)
    200                     Text(xTaler)
    201                         .monospacedDigit()
    202                         .padding(.leading)
    203                 }   .frame(maxWidth: .infinity, alignment: .leading)
    204                     .accessibilityElement(children: .combine)
    205                     .accessibilityLabel(Text("account of the recipient", comment: "a11y"))
    206                 CopyButton(textToCopy: xTaler, vertical: true)
    207                     .accessibilityLabel(Text("Copy the account", comment: "a11y"))
    208                     .disabled(false)
    209             }   .padding(.top, -8)
    210             let step1 = Text(minimalistic ? "**Step 1:** Copy+Paste this subject:"
    211                              : "**Step 1:** Copy this code and paste it into the subject/purpose field in your banking app or bank website:")
    212                 .talerFont(.body)
    213                 .multilineTextAlignment(.leading)
    214             let manda1 = String(localized: "This is mandatory, otherwise your money will not arrive in this wallet.")
    215             let manda2 = String(localized: "This is mandatory, otherwise the verification will fail.")
    216             let mandatory = Text(debitIBAN == nil ? manda1 : manda2)
    217                 .bold()
    218                 .talerFont(.body)
    219                 .multilineTextAlignment(.leading)
    220                 .listRowSeparator(.hidden)
    221             let step2i = Text(minimalistic ? "**Step 2:** Copy+Paste recipient and IBAN:"
    222                               : "**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):")
    223                 .talerFont(.body)
    224                 .multilineTextAlignment(.leading)
    225                 .padding(.top)
    226             let step2x = Text(minimalistic ? "**Step 2:** Copy+Paste recipient and account:"
    227                               : "**Step 2:** Copy and paste recipient and account into the corresponding fields in your banking app or website:")
    228                 .talerFont(.body)
    229                 .multilineTextAlignment(.leading)
    230                 .padding(.top)
    231             let step3A11y = String(localized: "Step 3: \(step3(amountStr.1))", comment: "a11y")
    232             let step3Head: LocalizedStringKey = "**Step 3:** \(step3(amountStr.0))"
    233             let step3 = Text(step3Head)
    234                 .accessibilityLabel(step3A11y)
    235                 .talerFont(.body)
    236                 .multilineTextAlignment(.leading)
    237 
    238             Group {
    239                 TransferRestrictionsV(amountStr: amountStr,
    240                                       obtainStr: obtainStr,
    241                                       debitIBAN: debitIBAN,
    242                                    restrictions: account.creditRestrictions)
    243                 .listRowSeparator(.visible)
    244                 step1
    245                 if !minimalistic {
    246                     mandatory
    247                 }
    248                 cryptocode
    249                 if iban != nil {
    250                     step2i
    251                     payeeCode
    252                     if let receiverZip {
    253                         if !receiverZip.isEmpty {
    254                             payeeZip()
    255                         }
    256                     }
    257                     if let receiverTown {
    258                         if !receiverTown.isEmpty {
    259                             payeeTown()
    260                         }
    261                     }
    262                     ibanCode
    263                 } else {
    264                     step2x
    265                     payeeCode
    266                     xTalerCode
    267                 }
    268                 amountCode
    269 //                    .padding(.top)
    270                 step3 // .padding(.top, 6)
    271             }.listRowSeparator(.hidden)
    272         }
    273         .navigationTitle(navTitle)
    274         .onAppear() {
    275 //            symLog.log("onAppear")
    276             DebugViewC.shared.setViewID(VIEW_WITHDRAW_INSTRUCTIONS, stack: stack.push())
    277         }
    278     }
    279 }
    280 
    281 // MARK: -
    282 #if DEBUG
    283 //struct ManualDetailsWire_Previews: PreviewProvider {
    284 //    static var previews: some View {
    285 //        let common = TransactionCommon(type: .withdrawal,
    286 //                              transactionId: "someTxID",
    287 //                                  timestamp: Timestamp(from: 1_666_666_000_000),
    288 //                                    txState: TransactionState(major: .done),
    289 //                                  txActions: [])
    290 //                            amountEffective: Amount(currency: LONGCURRENCY, cent: 110),
    291 //                                  amountRaw: Amount(currency: LONGCURRENCY, cent: 220),
    292 //        let payto = "payto://iban/SANDBOXX/DE159593?receiver-name=Exchange+Company"
    293 //        let details = WithdrawalDetails(type: .manual,
    294 //                                  reservePub: "ReSeRvEpUbLiC_KeY_FoR_WiThDrAwAl",
    295 //                              reserveIsReady: false,
    296 //                                   confirmed: false)
    297 //        List {
    298 //            ManualDetailsWireV(stack: CallStack("Preview"),
    299 //                             details: details,
    300 //                         receiverStr: <#T##String#>,
    301 //                                iban: <#T##String?#>,
    302 //                              xTaler: <#T##String#>,
    303 //                           amountStr: <#T##String#>,
    304 //                           obtainStr: <#T##String#>,
    305 //                             account: T##ExchangeAccountDetails)
    306 //        }
    307 //    }
    308 //}
    309 #endif