taler-ios

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

SettingsView.swift (21271B)


      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 taler_swift
     10 import SymLog
     11 import LocalConsole
     12 
     13 /*
     14  * Backup
     15  * Last backup: 5 hr. ago
     16  *
     17  * Debug log
     18  * View/send internal log
     19  *
     20  */
     21 
     22 struct SettingsView: View {
     23     private let symLog = SymLogV(0)
     24     let stack: CallStack
     25     let navTitle: String
     26 
     27     @EnvironmentObject private var controller: Controller
     28     @EnvironmentObject private var model: WalletModel
     29     @EnvironmentObject private var biometricService: BiometricService
     30 //    @Environment(\.colorSchemeContrast) private var colorSchemeContrast
     31 #if DEBUG
     32     @AppStorage("developerMode") var developerMode: Bool = true
     33 #else
     34     @AppStorage("developerMode") var developerMode: Bool = false
     35 #endif
     36     @AppStorage("useHaptics") var useHaptics: Bool = true
     37     @AppStorage("playSoundsI") var playSoundsI: Int = 1
     38     @AppStorage("playSoundsB") var playSoundsB: Bool = false
     39     @AppStorage("shouldShowWarning") var shouldShowWarning: Bool = true
     40 //    @AppStorage("increaseContrast") var increaseContrast: Bool = false
     41     @AppStorage("talerFontIndex") var talerFontIndex: Int = 0
     42     @AppStorage("developDelay") var developDelay: Bool = false
     43     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
     44     @AppStorage("minimalistic") var minimalistic: Bool = false
     45     @AppStorage("localConsoleL") var localConsoleL: Bool = false                // for Logs
     46     @AppStorage("localConsoleO") var localConsoleO: Int = 0                     // for Observability
     47     @AppStorage("useAuthentication") var useAuthentication: Bool = false
     48     @AppStorage("showQRauto16") var showQRauto16: Bool = true
     49     @AppStorage("showQRauto17") var showQRauto17: Bool = false
     50     @AppStorage("oimEuro") var oimEuro: Bool = false
     51     @AppStorage("oimChart") var oimChart: Bool = false
     52 
     53     @State private var checkDisabled = false
     54     @State private var withDrawDisabled = false
     55 #if DEBUG
     56     @State private var diagnosticModeEnabled = true
     57 #endif
     58     @State private var showDevelopItems = false
     59     @State private var hideDescriptions = false
     60     @State private var showResetAlert: Bool = false
     61     @State private var didReset: Bool = false
     62 
     63     private var dismissAlertButton: some View {
     64         Button("Cancel", role: .cancel) {
     65             showResetAlert = false
     66         }
     67     }
     68     private var resetButton: some View {
     69         Button("Reset", role: .destructive) {                                   // TODO: WalletColors().errorColor
     70             didReset = true
     71             showResetAlert = false
     72             Task { // runs on MainActor
     73                 symLog.log("❗️Reset wallet-core❗️")
     74                 try? await model.resetWalletCore()
     75             }
     76         }
     77     }
     78     @State private var listID = UUID()
     79 
     80     func redraw(_ newFont: Int) -> Void {
     81         if newFont != talerFontIndex {
     82             talerFontIndex = newFont
     83             withAnimation { listID = UUID() }
     84         }
     85     }
     86     var body: some View {
     87 #if PRINT_CHANGES
     88         let _ = Self._printChanges()
     89         let _ = symLog.vlog()       // just to get the # to compare it with .onAppear & onDisappear
     90 #endif
     91         let walletCore = WalletCore.shared
     92         Group {
     93             List {
     94 #if TALER_WALLET
     95                 let appName = "Taler Wallet"
     96 #elseif TALER_NIGHTLY
     97                 let appName = "Taler Nightly"
     98 #else
     99                 let appName = "GNU Taler"
    100 #endif
    101                 let localizedAppName = Bundle.main.bundleName ?? appName
    102                 let aboutStr = String(localized: "About \(localizedAppName)")
    103                 NavigationLink {        // whole row like in a tableView
    104                     AboutView(stack: stack.push(), navTitle: aboutStr)
    105                 } label: {
    106                     SettingsItem(name: aboutStr, id1: "about",
    107                           description: hideDescriptions ? nil : String(localized: "More info about this app...")) {}
    108                 }
    109 
    110                 let exchangesTitle = String(localized: "TitleExchanges", defaultValue: "Payment Services")
    111                 let exchangesDest = ExchangeListView(stack: stack.push(exchangesTitle),
    112                                                   navTitle: exchangesTitle)
    113                 NavigationLink {        // whole row like in a tableView
    114                     exchangesDest
    115                 } label: {
    116                     SettingsItem(name: exchangesTitle, id1: "exchanges",
    117                           description: hideDescriptions ? nil : String(localized: "Manage payment services...")) {}
    118                 }
    119                 let bankAccountsTitle = String(localized: "TitleBankAccounts", defaultValue: "Bank Accounts")
    120                 let bankAccountsDest = BankListView(stack: stack.push(bankAccountsTitle),
    121                                                  navTitle: bankAccountsTitle)
    122                 NavigationLink {        // whole row like in a tableView
    123                     bankAccountsDest
    124                 } label: {
    125                     SettingsItem(name: bankAccountsTitle, id1: "bankAccounts",
    126                                  description: hideDescriptions ? nil : String(localized: "Your accounts for deposit...")) {}
    127                 }
    128                 let showQRstring = String(localized: "Show QR codes")
    129                 let showQRhint = String(localized: "Automatically for P2P transactions")
    130                 if #available(iOS 17.7, *) {
    131                     SettingsToggle(name: showQRstring, value: $showQRauto17, id1: "showQRautomatic",
    132                             description: minimalistic ? nil : showQRhint) {}
    133                 } else {
    134                     SettingsToggle(name: showQRstring, value: $showQRauto16, id1: "showQRautomatic",
    135                             description: minimalistic ? nil : showQRhint) {}
    136                 }
    137                 SettingsToggle(name: String(localized: "Use FaceID / TouchID"), value: $useAuthentication, id1: "useFaceID",
    138                         description: minimalistic ? nil : String(localized: "Protect your money")) {
    139                     biometricService.isAuthenticated = false
    140                 }
    141 
    142                 /// Backup
    143                 let backupTitle = String(localized: "TitleBackup", defaultValue: "Backup / Restore")
    144                 let backupDest = BackupView(stack: stack.push(backupTitle),
    145                                          navTitle: backupTitle)
    146                 NavigationLink {        // whole row like in a tableView
    147                     backupDest
    148                 } label: {
    149                     SettingsItem(name: backupTitle, id1: "backup",
    150                           description: hideDescriptions ? nil : String(localized: "Backup your money...")) {}
    151                 }
    152 
    153 #if OIM
    154                 SettingsToggle(name: String(localized: "OIM: Euro"), value: $oimEuro, id1: "oimEuro",
    155                         description: minimalistic ? nil : String(localized: "OIM currency for KUDOS"))
    156 #endif
    157 #if OIM
    158                 SettingsToggle(name: String(localized: "OIM: Chart"), value: $oimChart, id1: "oimChart",
    159                         description: minimalistic ? nil : String(localized: "OIM history as chart"))
    160 #endif
    161 
    162                 SettingsToggle(name: String(localized: "Minimalistic"), value: $minimalistic, id1: "minimal",
    163                         description: hideDescriptions ? nil : String(localized: "Omit text where possible")) {
    164                     hideDescriptions = minimalistic //withAnimation { hideDescriptions = minimalistic }
    165                 }
    166                 if controller.hapticCapability.supportsHaptics {
    167                     SettingsToggle(name: String(localized: "Haptics"), value: $useHaptics, id1: "haptics",
    168                             description: hideDescriptions ? nil : String(localized: "Vibration Feedback"))
    169                 }
    170                 SettingsToggle(name: String(localized: "Play Payment Sounds"), value: $playSoundsB, id1: "playSounds",
    171                         description: hideDescriptions ? nil : String(localized: "When a transaction finished"))
    172                 SettingsToggle(name: String(localized: "Show Warnings"), value: $shouldShowWarning, id1: "warnings",
    173                         description: hideDescriptions ? nil : String(localized: "For Delete, Abandon & Abort buttons"))
    174 //                SettingsFont(title: String(localized: "Font:"), value: talerFontIndex, action: redraw)
    175 //                    .id("font")
    176                 SettingsStyle(title: String(localized: "List Style:"), myListStyle: $myListStyle)
    177                     .id("liststyle")
    178 #if DEBUG
    179                 let showDiagnostic = diagnosticModeEnabled
    180 #else
    181                 let showDiagnostic = controller.diagnosticModeEnabled
    182 #endif
    183                 if showDiagnostic {
    184                     let localConsStr = String("on LocalConsole")
    185                     let observability = String("Observe walletCore")
    186                     SettingsTriState(name: observability, value: $localConsoleO.onChange({ isObserving in
    187                         walletCore.isObserving = isObserving}),
    188                               description: hideDescriptions ? nil : localConsStr) { isObserving in
    189                         let consoleManager = LCManager.shared
    190                         consoleManager.isVisible = localConsoleO != 0 || localConsoleL
    191                         consoleManager.clear()
    192                     }
    193                     let showLogs = String("Show logs")
    194                     SettingsToggle(name: showLogs, value: $localConsoleL.onChange({ isLogging in
    195                         walletCore.isLogging = isLogging}), id1: "localConsoleL",
    196                             description: hideDescriptions ? nil : localConsStr) {
    197                         let consoleManager = LCManager.shared
    198                         consoleManager.isVisible = localConsoleO != 0 || localConsoleL
    199                         consoleManager.clear()
    200                     }
    201                     SettingsToggle(name: String("Developer Mode"), value: $developerMode, id1: "devMode",
    202                             description: hideDescriptions ? nil : String("More information intended for debugging")) {
    203                         withAnimation(Animation.linear.delay(0.8)) { showDevelopItems = developerMode }
    204                     }
    205 #if DEBUG
    206                     if showDevelopItems {
    207                         let banks = ["glstest.taler.net", "glsint.fdold.eu", "taler.fdold.eu", "regio-taler.fdold.eu",
    208                                      "taler.grothoff.org", "taler.ar",
    209                                      "head.taler.net", "test.taler.net", "demo.taler.net", "kyctest.taler.net"]
    210                         ForEach(banks, id: \.self) { bank in
    211                             let urlStr = "https://bank." + bank
    212                             Link(bank, destination: URL(string: urlStr)!)
    213                         }
    214                     }
    215 #endif
    216                     if showDevelopItems {  // show or hide the following items
    217                         SettingsItem(name: String("DEMO"), id1: "demo1with",
    218                               description: hideDescriptions ? nil : String("Get money for testing")) {
    219                             let title = "Withdraw"
    220                             Button(title) {
    221                                 withDrawDisabled = true    // don't run twice
    222                                 Task { // runs on MainActor
    223                                     symLog.log("Withdraw DEMO KUDOS")
    224                                     let amount = Amount(currency:  DEMOCURRENCY, cent: 11100)
    225                                     try? await model.loadTestKudos(0, amount: amount)
    226                                 }
    227                             }
    228                             .buttonStyle(.bordered)
    229                             .disabled(withDrawDisabled)
    230                         }.id("demo1withdraw")
    231                         SettingsItem(name: String("TEST"), id1: "test1with",
    232                               description: hideDescriptions ? nil : String("Get money for testing")) {
    233                             let title = "Withdraw"
    234                             Button(title) {
    235                                 withDrawDisabled = true    // don't run twice
    236                                 Task { // runs on MainActor
    237                                     symLog.log("Withdraw TESTKUDOS")
    238                                     let cent = UInt64.random(in: 110...195) * 100
    239                                     let amount = Amount(currency:  TESTCURRENCY, cent: cent)
    240                                     try? await model.loadTestKudos(1, amount: amount)
    241                                 }
    242                             }
    243                             .buttonStyle(.bordered)
    244                             .disabled(withDrawDisabled)
    245                         }.id("test1withdraw")
    246                         SettingsItem(name: String("HEAD"), id1: "head1with",
    247                               description: hideDescriptions ? nil : String("Get money for testing")) {
    248                             let title = "Withdraw"
    249                             Button(title) {
    250                                 withDrawDisabled = true    // don't run twice
    251                                 Task { // runs on MainActor
    252                                     symLog.log("Withdraw HEAD KUDOS")
    253                                     let amount = Amount(currency:  DEMOCURRENCY, cent: 1100)
    254                                     try? await model.loadTestKudos(2, amount: amount)
    255                                 }
    256                             }
    257                             .buttonStyle(.bordered)
    258                             .disabled(withDrawDisabled)
    259                         }.id("head1withdraw")
    260                         SettingsToggle(name: String("Set 2 seconds delay"),
    261                                       value: $developDelay.onChange({ delay in
    262                                                 walletCore.developDelay = delay}),
    263                                         id1: "delay",
    264                                 description: hideDescriptions ? nil : String("After each wallet-core action"))
    265                             .id("delay")
    266 #if DEBUG
    267                         SettingsItem(name: String("Run Dev Experiment Refresh"), id1: "applyDevExperiment",
    268                               description: hideDescriptions ? nil : "dev-experiment/insert-pending-refresh") {
    269                             let title = "Refresh"
    270                             Button(title) {
    271                                 Task { // runs on MainActor
    272                                     symLog.log("running applyDevExperiment Refresh")
    273                                     try? await model.setConfig(setTesting: true)
    274                                     try? await model.devExperimentT(talerUri: "taler://dev-experiment/start-block-refresh")
    275                                     try? await model.devExperimentT(talerUri: "taler://dev-experiment/insert-pending-refresh")
    276                                 }
    277                             }
    278                             .buttonStyle(.bordered)
    279                         }.id("Refresh")
    280 #endif
    281                         SettingsItem(name: String("Run Integration Test"), id1: "demo1test",
    282                               description: hideDescriptions ? nil : String("Perform basic test transactions")) {
    283                             let title = "Demo 1"
    284                             Button(title) {
    285                                 checkDisabled = true    // don't run twice
    286                                 Task { // runs on MainActor
    287                                     symLog.log("running integration test on demo")
    288                                     try? await model.runIntegrationTest(newVersion: false, test: false)
    289                                 }
    290                             }
    291                             .buttonStyle(.bordered)
    292                             .disabled(checkDisabled)
    293                         }.id("demo1runTest")
    294                         SettingsItem(name: String("Run Integration Test"), id1: "test1test",
    295                               description: hideDescriptions ? nil : "Perform basic test transactions") {
    296                             let title = "Test 1"
    297                             Button(title) {
    298                                 checkDisabled = true    // don't run twice
    299                                 Task { // runs on MainActor
    300                                     symLog.log("running integration test on test")
    301                                     try? await model.runIntegrationTest(newVersion: false, test: true)
    302                                 }
    303                             }
    304                             .buttonStyle(.bordered)
    305                             .disabled(checkDisabled)
    306                         }.id("test1runTest")
    307                         SettingsItem(name: String("Run Integration Test V2"), id1: "demo2test",
    308                               description: hideDescriptions ? nil : String("Perform more test transactions")) {
    309                             let title = "Demo 2"
    310                             Button(title) {
    311                                 checkDisabled = true    // don't run twice
    312                                 Task { // runs on MainActor
    313                                     symLog.log("running integration test V2 on demo")
    314                                     try? await model.runIntegrationTest(newVersion: true, test: false)
    315                                 }
    316                             }
    317                             .buttonStyle(.bordered)
    318                             .disabled(checkDisabled)
    319                         }.id("demo2runTest")
    320                         SettingsItem(name: String("Run Integration Test V2"), id1: "test2test",
    321                               description: hideDescriptions ? nil : String("Perform more test transactions")) {
    322                             let title = "Test 2"
    323                             Button(title) {
    324                                 checkDisabled = true    // don't run twice
    325                                 Task { // runs on MainActor
    326                                     symLog.log("running integration test V2 on test")
    327                                     try? await model.runIntegrationTest(newVersion: true, test: true)
    328                                 }
    329                             }
    330                             .buttonStyle(.bordered)
    331                             .disabled(checkDisabled)
    332                         }.id("test2runTest")
    333                         SettingsItem(name: String("Run Infinite Transaction Loop"), id1: "runInfinite",
    334                               description: hideDescriptions ? nil : String("Check DB in background")) {
    335                             let title = "Loop"
    336                             Button(title) {
    337                                 checkDisabled = true    // don't run twice
    338                                 Task { // runs on MainActor
    339                                     symLog.log("Running Infinite Transaction Loop")
    340                                     try? await model.testingInfiniteTransaction(delayMs: 10_000, shouldFetch: true)
    341                                 }
    342                             }
    343                             .buttonStyle(.bordered)
    344                             .disabled(checkDisabled)
    345                         }.id("runInfiniteLoop")
    346                         SettingsItem(name: String("Save Logfile"), id1: "save",
    347                               description: hideDescriptions ? nil : String("Help debugging wallet-core")) {
    348                             Button("Save") {
    349                                 symLog.log("Saving Log")
    350                                 // FIXME: Save Logfile
    351                             }
    352                             .buttonStyle(.bordered)
    353                             .disabled(true)
    354                         }.id("saveLog")
    355                         SettingsItem(name: String("Reset Wallet"), id1: "reset",
    356                               description: hideDescriptions ? nil : String("Throw away all your money")) {
    357                             Button("Reset") {
    358                                 showResetAlert = true
    359                             }
    360                             .buttonStyle(.bordered)
    361 //                            .disabled(didReset)
    362                         }.id("resetWallet")
    363                     }
    364                 }
    365             }
    366             .id(listID)
    367             .listStyle(myListStyle.style).anyView
    368         }
    369         .navigationTitle(navTitle)
    370         .onAppear() {
    371             showDevelopItems = developerMode
    372             hideDescriptions = minimalistic
    373             DebugViewC.shared.setViewID(VIEW_SETTINGS, stack: stack.push())
    374         }
    375         .onDisappear() {
    376             checkDisabled = false    // reset
    377             withDrawDisabled = false
    378         }
    379         .alert("Reset Wallet",
    380                isPresented: $showResetAlert,
    381                actions: { dismissAlertButton
    382                           resetButton },
    383                message: {   Text(verbatim: "Are you sure you want to reset your wallet?\nThis cannot be reverted, all money will be lost.") })
    384 
    385     } // body
    386 }
    387 // MARK: -
    388 #if DEBUG
    389 //struct SettingsView_Previews: PreviewProvider {
    390 //    static var previews: some View {
    391 //        SettingsView(stack: CallStack("Preview"), balances: <#Binding<[Balance]>#>, navTitle: "Settings")
    392 //    }
    393 //}
    394 #endif