taler-ios

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

WalletEmptyView.swift (10278B)


      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 SymLog
     10 import taler_swift
     11 
     12 struct ProdSectionView: View {
     13     let stack: CallStack
     14     let isEmpty: Bool
     15     let disabled: Bool
     16 
     17     @Environment(\.colorScheme) private var colorScheme
     18     @Environment(\.colorSchemeContrast) private var colorSchemeContrast
     19     @EnvironmentObject private var controller: Controller
     20     @EnvironmentObject private var model: WalletModel
     21 #if DEBUG
     22     @AppStorage("developerMode") var developerMode: Bool = true
     23 #else
     24     @AppStorage("developerMode") var developerMode: Bool = false
     25 #endif
     26 
     27     @State private var selectedCurrency: Int = 0
     28     @State private var showDropdown = true
     29 
     30     func buttonAction() {
     31         withAnimation {
     32             showDropdown.toggle()
     33         }
     34     }
     35 
     36     var body: some View {
     37         let defaultExchanges = controller.defaultExchanges
     38         let count = defaultExchanges.count
     39         Section {
     40             let chevron = Image(systemName: CHEVRON_UP)     // 􀆇
     41             if isEmpty {
     42                 Text("Welcome to Taler Wallet!")
     43                     .talerFont(.headline)
     44                 Text("To make your first payment, withdraw digital cash.")
     45                     .talerFont(.body)
     46             } else {
     47                 Button(action: buttonAction) {
     48                     HStack(alignment: .firstTextBaseline) {
     49                         Text("Currently you have only demo cash.")
     50                             .talerFont(.body)
     51                             .accessibilityAddTraits(.isSelected)
     52                         Spacer()
     53                         chevron.rotationEffect(.degrees((showDropdown ?  -180 : 0)))
     54                             .accessibilityHidden(true)
     55                     }
     56                     .contentShape(.rect)    // can tap in background also
     57                 }   .buttonStyle(.plain)
     58                 if (showDropdown) {
     59                     Text("Withdraw real digital cash:")
     60                         .talerFont(.body)
     61                         .padding(.vertical, -16)
     62                 }
     63             }
     64 #if false
     65             let qrButton = Image(systemName: QRBUTTON)                      // 􀎻 "qrcode.viewfinder"
     66             let settings = Image(systemName: SETTINGS)                      // 􀍟 "gear"
     67             Text("Use «\(qrButton) Scan QR code» in the Actions menu to start a withdrawal if your bank already supports Taler payments.", comment: "« 􀎻 » 'qrcode.viewfinder'")
     68                 .talerFont(.body)
     69             Text("You can also add a payment service manually in the \(settings) Settings tab.", comment: "« 􀍟 » 'gear'")
     70                 .talerFont(.body)
     71 #endif
     72             if (showDropdown) {
     73 #if !TALER_WALLET
     74                 if developerMode {
     75                     if defaultExchanges.count > 1 {
     76                         CurrencyPicker(stack: stack.push(), value: $selectedCurrency,
     77                                    exchanges: defaultExchanges) { index in
     78                             selectedCurrency = index
     79                         }
     80                     }
     81                 }
     82 #endif
     83                 let defaultExchange = defaultExchanges[selectedCurrency < count ? selectedCurrency : 0]
     84                 let currency = defaultExchange.currency
     85                 let title = String(localized: "LinkTitle_Withdraw", defaultValue: "Withdraw \(currency)")
     86                 Button(title) {
     87                     if let talerUri = URL(string: defaultExchange.talerUri) {
     88                         controller.talerURI = talerUri
     89                     }
     90                 }
     91                 .buttonStyle(TalerButtonStyle(type: isEmpty ? .prominent : .bordered,
     92                                             narrow: false,
     93                                           disabled: disabled,
     94                                            aligned: .center))
     95                 .padding(.top, -6)
     96                 .padding(.bottom, 6)
     97             } // showDropdown
     98         } header: {
     99             if isEmpty {
    100                 let firstHeader = EMPTYSTRING
    101                 Text(firstHeader)
    102                     .talerFont(.badge)
    103 //                    .foregroundColor(.primary)
    104                 /// YIKES! when not specifying this color, the Text in the BarGraphHeader vanishes!!!
    105                     .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast))
    106 //                    .listRowInsets(EdgeInsets())  // << here !!
    107             }
    108         }
    109         .listRowSeparator(.hidden)
    110 //        .listSectionSpacing(.compact) iOS 17
    111     }
    112 }
    113 
    114 /// This view shows hints if a wallet is empty
    115 /// It is the very first thing the user sees after installing the app
    116 
    117 struct WalletEmptyHeader: View {
    118     let stack: CallStack
    119 
    120     @EnvironmentObject private var model: WalletModel
    121     @EnvironmentObject private var controller: Controller
    122     @Environment(\.accessibilityVoiceOverEnabled) private var voiceOverEnabled
    123 
    124     func refresh() async {
    125         controller.hapticNotification(.success)
    126 //        symLog.log("refreshing balances")
    127         await controller.loadBalances(stack.push("refreshing balances"), model)
    128     }
    129 
    130     var body: some View {
    131         let talerLogo = HStack(spacing: 2) {
    132             Image(TALER_LOGO_FULL)
    133 //                .border(Color.gray, width: 1)
    134 //            Text("TALER Wallet")
    135             Text("Wallet")
    136 //                .font(.logo(27, weight: .medium))
    137                 .font(.logo("Atkinson Hyperlegible Next", size: 27, weight: .medium))
    138                 .kerning(1.0)
    139                 .fixedSize(horizontal: true, vertical: true)
    140         }
    141             .accessibilityElement(children: .combine)
    142             .accessibilityAddTraits(.isHeader)
    143             .accessibilityLabel(Text("Taler Wallet", comment: "a11y"))
    144 
    145         let emptyView =  WalletEmptyView(stack: stack.push("isEmpty"))
    146         //                  .navigationTitle("Taler Wallet")
    147             .refreshable {
    148                 await refresh()
    149             }
    150 
    151         let logoItem = ToolbarItem(placement: .principal) {
    152             talerLogo
    153                 .padding(.top, 10)
    154         }
    155         if #available(iOS 26.0, *) {
    156             if voiceOverEnabled {
    157                 emptyView
    158                     .toolbar { logoItem }
    159             } else {
    160                 emptyView
    161                     .toolbar {
    162                         ToolbarItem(placement: .principal) {
    163                             HStack {
    164                                 SettingsButton26(sortPriority: 0)
    165                                     .hidden()
    166                                 Spacer()
    167                                 talerLogo
    168                                     .padding(.top, 10)
    169                                 Spacer()
    170                                 SettingsButton26(sortPriority: 0)
    171                                     .offset(x: 16, y: 0)
    172                             }
    173                         }
    174                     }
    175             }
    176         } else { // iOS 15..18 Settings is the 3rd tab
    177             emptyView
    178                 .toolbar { logoItem }
    179         }
    180     }
    181 }
    182 
    183 struct WalletEmptyView: View {
    184     private let symLog = SymLogV(0)
    185     let stack: CallStack
    186 
    187     @EnvironmentObject private var controller: Controller
    188     @EnvironmentObject private var model: WalletModel
    189     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
    190     @State private var withDrawStarted = false
    191     @State private var urlToOpen: URL? = nil
    192 
    193     var body: some View {
    194         let list = List {
    195             if !controller.defaultExchanges.isEmpty {
    196                 ProdSectionView(stack: stack.push(), isEmpty: true, disabled: withDrawStarted)
    197             }
    198 #if GNU_TALER
    199                 Text("If you don't have a Swiss bank account, you can simply try out the demo…")
    200                     .talerFont(.body)
    201 #endif
    202 
    203 #if DEBUG
    204 //              if developerMode {
    205 //                Text("Either deposit or send your money to a friend before you delete the app, or it will be lost.")
    206 //                    .talerFont(.body)
    207 //              }
    208 #endif
    209             Section {
    210 //                Text("Demo")
    211 //                    .talerFont(.headline)
    212 //                Text("Get digital cash to experience how easy you can pay online, and send money to friends & family.")
    213                 Text("Get demo cash to experience how to pay with digital cash.")
    214 //                Text("Get digital cash to experience how to pay with the money of the future.")
    215                     .talerFont(.body)
    216                     .listRowSeparator(.hidden)
    217                 let title = String(localized: "LinkTitle_Test_Money", defaultValue: "Get demo cash")
    218                 Button(title) {
    219                     withDrawStarted = true    // don't run twice
    220                     Task { // runs on MainActor
    221                         let amount = Amount(currency:  DEMOCURRENCY, cent: 2500)
    222                         symLog.log("Withdraw KUDOS")
    223                         try? await model.loadTestKudos(0, amount: amount)
    224                         DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    225                             withDrawStarted = false
    226                         }
    227                     }
    228                 }
    229                 .buttonStyle(TalerButtonStyle(type: .prominent, narrow: false, disabled: withDrawStarted, aligned: .center))
    230                 .disabled(withDrawStarted)
    231 //            } header: {
    232 //                let secondHeader = String(localized: "Demo", comment: "section header")
    233 //                Text(secondHeader)
    234 //                    .talerFont(.title3)
    235 //                    .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast))
    236             }
    237         }
    238             .listStyle(myListStyle.style).anyView
    239             .talerFont(.title2)
    240             .background(FullBackground())
    241         ZStack {
    242             list
    243             if withDrawStarted {
    244                 RotatingTaler(size: 150, progress: true, once: false, rotationEnabled: $withDrawStarted)
    245             }
    246         }
    247         .onAppear() {
    248             DebugViewC.shared.setViewID(VIEW_EMPTY_WALLET, stack: stack.push("onAppear"))     // 10
    249         }
    250     }
    251 }
    252 
    253 struct WalletEmptyView_Previews: PreviewProvider {
    254     static var previews: some View {
    255         WalletEmptyView(stack: CallStack("Preview"))
    256     }
    257 }