taler-ios

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

SettingsView.swift (10932B)


      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 taler_swift
     10 import SymLog
     11 
     12 struct SettingsView: View {
     13     private let symLog = SymLogV(0)
     14     let stack: CallStack
     15     let navTitle: String
     16 
     17     @EnvironmentObject private var controller: Controller
     18     @EnvironmentObject private var model: WalletModel
     19     @EnvironmentObject private var biometricService: BiometricService
     20 //    @Environment(\.colorSchemeContrast) private var colorSchemeContrast
     21 #if DEBUG
     22     @AppStorage("developerMode") var developerMode: Bool = true
     23 #else
     24     @AppStorage("developerMode") var developerMode: Bool = false
     25 #endif
     26     @AppStorage("shouldShowWarning") var shouldShowWarning: Bool = true
     27     @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
     28     @AppStorage("minimalistic") var minimalistic: Bool = false
     29     @AppStorage("useAuthentication") var useAuthentication: Bool = false
     30     @AppStorage("pushNotifications") var pushNotifications: Bool = false
     31 
     32     @State private var listID = UUID()
     33     @State private var mayNotUsePush: Bool = false
     34     @State private var registerState: Bool = false
     35 
     36     var isRegistered: Bool {
     37         UIApplication.shared.isRegisteredForRemoteNotifications
     38     }
     39 
     40     func checkRegisterState(delaySeconds: Double) {
     41         // update isRegistered ==> icon shown
     42         DispatchQueue.main.asyncAfter(deadline: .now() + delaySeconds) {
     43             registerState = isRegistered
     44         }
     45     }
     46 
     47     private var dismissAlertButton: some View {
     48         Button("Cancel", role: .cancel) {
     49             pushNotifications = false
     50             mayNotUsePush = false
     51         }
     52     }
     53 
     54     func checkPushNotifications(_ shouldUsePush: Bool) {
     55 #if TALER_NIGHTLY
     56         // 1. Request authorisation for remote (push) notifications.
     57         //    For background-only (silent) pushes the alert/badge/sound
     58         //    entitlements are not strictly required, but requesting them
     59         //    avoids surprises when you later add user-visible notifications.
     60         DispatchQueue.main.async {
     61             if isRegistered {
     62                 registerState = true
     63                 if shouldUsePush {
     64                     self.symLog.log("already registered for remote notifications")
     65                 } else {
     66                     self.symLog.log("unregisterForRemoteNotifications")
     67                     UIApplication.shared.unregisterForRemoteNotifications()
     68                     controller.deviceTokenAPNs = nil                            // TODO: tell walletCore
     69                     checkRegisterState(delaySeconds: 0.5)
     70                 }
     71             } else {
     72                 registerState = false
     73                 if !shouldUsePush {
     74                     self.symLog.log("remote notifications are disabled")
     75                 } else {
     76                     UNUserNotificationCenter.current().requestAuthorization(
     77                         options: [.alert, .badge, .sound]
     78                     ) { granted, error in
     79                         DispatchQueue.main.async {
     80                             if granted {
     81                                 self.symLog.log("registerForRemoteNotifications")
     82                                 /// result in didRegisterForRemoteNotificationsWithDeviceToken
     83                                 UIApplication.shared.registerForRemoteNotifications()
     84                                 checkRegisterState(delaySeconds: 0.5)
     85                             } else {
     86                                 self.symLog.log("Error requesting notification permissions: \(error?.localizedDescription ?? "unknown")")
     87                                 mayNotUsePush = true
     88                                 registerState = false
     89                                 pushNotifications = false
     90                             }
     91                         }
     92                     }
     93                 }
     94             }
     95         }
     96 #endif
     97 
     98     }
     99 
    100 
    101     var body: some View {
    102 #if PRINT_CHANGES
    103         let _ = Self._printChanges()
    104         let _ = symLog.vlog()       // just to get the # to compare it with .onAppear & onDisappear
    105 #endif
    106 #if TALER_WALLET
    107         let appName = "Taler Wallet"
    108 #elseif TALER_NIGHTLY
    109         let appName = "Taler Nightly"
    110 #else
    111         let appName = "GNU Taler"
    112 #endif
    113         let localizedAppName = Bundle.main.bundleName ?? appName
    114         let list = List {
    115                 let aboutStr = String(localized: "About \(localizedAppName)")
    116                 NavigationLink {        // whole row like in a tableView
    117                     AboutView(stack: stack.push(), navTitle: aboutStr)
    118                 } label: {
    119                     SettingsItem(name: aboutStr, id1: "about", imageName: TALER_LOGO) {}
    120                 }
    121 
    122                 let exchangesTitle = String(localized: "TitleExchanges", defaultValue: "Payment Services")
    123                 let exchangesDest = ExchangeListView(stack: stack.push(exchangesTitle),
    124                                                   navTitle: exchangesTitle)
    125                 NavigationLink {        // whole row like in a tableView
    126                     exchangesDest
    127                 } label: {
    128                     SettingsItem(name: exchangesTitle, id1: "exchanges", imageName: EXCHANGE_LOGO,
    129                           description: String(localized: "Manage payment services")) {}
    130                 }
    131                 let bankAccountsTitle = String(localized: "TitleBankAccounts", defaultValue: "Bank Accounts")
    132                 let bankAccountsDest = BankListView(stack: stack.push(bankAccountsTitle),
    133                                                  navTitle: bankAccountsTitle)
    134                 NavigationLink {        // whole row like in a tableView
    135                     bankAccountsDest
    136                 } label: {
    137                     SettingsItem(name: bankAccountsTitle, id1: "bankAccounts",
    138                             imageName: "building.columns",
    139                           description: String(localized: "Your accounts for deposit")) {}
    140                 }
    141 
    142                 let biometryType = biometricService.biometryType()
    143                 let hasFaceID = biometryType == .faceID
    144                 let hasTouchID = biometryType == .touchID
    145                 let biometryString = hasFaceID ? String(localized: "Use FaceID")
    146                                    : hasTouchID ? String(localized: "Use TouchID")
    147                                    : EMPTYSTRING
    148                 if !biometryString.isEmpty {
    149                     SettingsToggle(name: biometryString,
    150                                   value: $useAuthentication,
    151                                     id1: "useFaceID",
    152                               imageName: hasFaceID ? "faceid" : "touchid", // 􀎽  􀟒
    153                             description: String(localized: "Protect your money")) { _ in
    154                         biometricService.isAuthenticated = false
    155                     }
    156 //                } else {
    157 //                    biometricService.isAuthenticated = false
    158                 }
    159 
    160 #if TALER_NIGHTLY
    161                 SettingsToggle(name: String(localized: "Push Notifications"), value: $pushNotifications,
    162                                 id1: "pushNotifications",
    163                           imageName: registerState ? NOTIFICATION2 : NOTIFICATION1,  // 􀝖 or 􀋙
    164                         description: String(localized: "Check pending payments in the background")
    165                 ) { newVal in
    166                     checkPushNotifications(newVal)
    167                 }
    168 #endif
    169                 SettingsToggle(name: String(localized: "Minimalistic"), value: $minimalistic, id1: "minimal",
    170                           imageName: "heart",                   // 􀊴
    171                         description: String(localized: "Omit text where possible"))
    172 
    173                 SettingsToggle(name: String(localized: "Show Warnings"), value: $shouldShowWarning,
    174                                 id1: "warnings",
    175                           imageName: "exclamationmark.triangle", // 􀇾
    176                         description: String(localized: "For Delete, Abandon & Abort buttons"))
    177 
    178               /// Report
    179                 let reportTitle = String(localized: "TitleReport", defaultValue: "Report diagnostics")
    180                 let reportDest = ReportView(stack: stack.push(reportTitle),
    181                                          navTitle: reportTitle)
    182                 NavigationLink {
    183                     reportDest
    184                 } label: {
    185                     SettingsItem(name: reportTitle, id1: "report",
    186                             imageName: "arrow.up.message",    // 􀜃
    187                           description: String(localized: "Help improve \(localizedAppName)")) {}
    188                 }
    189 
    190 #if DEBUG
    191                 let showDiagnostic = true
    192 #else
    193                 let showDiagnostic = controller.diagnosticModeEnabled
    194 #endif
    195                 if showDiagnostic {
    196                     let devTitle = String(localized: "TitleDeveloper", defaultValue: "Developer")
    197                     let devDest = DebugSettingsView(stack: stack.push(devTitle),
    198                                                  navTitle: devTitle)
    199                     NavigationLink {
    200                         devDest
    201                     } label: {
    202                         SettingsItem(name: devTitle, id1: "developer",
    203                                 imageName: "hammer",        // 􀙄
    204                               description: String(localized: "Help debug \(localizedAppName)")) {}
    205                     }
    206                 }
    207 
    208                 let moreItem = String(localized: "TitleMore", defaultValue: "More")
    209                 let moreTitle = String(localized: "TitleMoreSettings", defaultValue: "More Settings")
    210                 let moreDest = MoreSettingsView(stack: stack.push(moreTitle),
    211                                              navTitle: moreTitle)
    212                 NavigationLink {
    213                     moreDest
    214                 } label: {
    215                     SettingsItem(name: moreItem, id1: "more",
    216                             imageName: "ellipsis",          // 􀍠
    217                           description: nil) {}
    218                 }
    219         }
    220             .padding(.bottom)
    221             .id(listID)
    222             .listStyle(myListStyle.style).anyView
    223 
    224         list
    225             .navigationTitle(navTitle)
    226             .onAppear() {
    227                 DebugViewC.shared.setViewID(VIEW_SETTINGS, stack: stack.push())
    228                 registerState = isRegistered
    229             }
    230             .alert("Push Notifications are disabled",
    231                    isPresented: $mayNotUsePush,
    232                    actions: { dismissAlertButton },
    233                    message: { Text("Please go to Settings > \(localizedAppName) > Notifications and turn them on.") }
    234             )
    235     } // body
    236 }
    237 // MARK: -
    238 #if DEBUG
    239 //struct SettingsView_Previews: PreviewProvider {
    240 //    static var previews: some View {
    241 //        SettingsView(stack: CallStack("Preview"), balances: <#Binding<[Balance]>#>, navTitle: "Settings")
    242 //    }
    243 //}
    244 #endif