taler-ios

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

WalletEmptyView.swift (12103B)


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