taler-ios

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

commit a1d90c5bd0c74a5272de57f1f3e9a3a28d220acb
parent aeab8aa5d8200e5605cb45cb8d9e7aa890ff242b
Author: Marc Stibane <marc@taler.net>
Date:   Thu,  5 Jun 2025 16:16:38 +0200

Use BiometricService

Diffstat:
MTalerWallet1/Controllers/TalerWallet1App.swift | 5+++++
MTalerWallet1/GNU_Taler InfoPlist.xcstrings | 48++++++++++++++++++++++++++++++++++++++++++++++++
MTalerWallet1/Taler_Nightly InfoPlist.xcstrings | 24++++++++++++++++++++++++
MTalerWallet1/Taler_Wallet InfoPlist.xcstrings | 24++++++++++++++++++++++++
MTalerWallet1/Views/Main/MainView.swift | 41+++++++++++++++++++++++++++++++++++++++--
MTalerWallet1/Views/Settings/SettingsView.swift | 11++++++-----
6 files changed, 146 insertions(+), 7 deletions(-)

diff --git a/TalerWallet1/Controllers/TalerWallet1App.swift b/TalerWallet1/Controllers/TalerWallet1App.swift @@ -27,6 +27,7 @@ struct TalerWallet1App: App { private let model = WalletModel.shared private let debugViewC = DebugViewC.shared let logger = Logger(subsystem: "net.taler.gnu", category: "Main App") + private let biometricService = BiometricService.shared func scheduleAppRefresh() { let request = BGAppRefreshTaskRequest(identifier: "net.taler.gnu.refresh") @@ -47,6 +48,7 @@ struct TalerWallet1App: App { .environmentObject(viewState2) // popToRoot .environmentObject(controller) .environmentObject(model) + .environmentObject(biometricService) .addKeyboardVisibilityToEnvironment() /// external events are taler:// or payto:// URLs passed to this app /// we handle them in .onOpenURL in MainView.swift @@ -66,6 +68,9 @@ struct TalerWallet1App: App { logger.log("More than 5 minutes in background - tell wallet-core") hintApplicationResumed() } + if interval.seconds > 60 { + biometricService.isAuthenticated = false + } } backgrounded = nil } diff --git a/TalerWallet1/GNU_Taler InfoPlist.xcstrings b/TalerWallet1/GNU_Taler InfoPlist.xcstrings @@ -5,6 +5,12 @@ "comment" : "Bundle display name", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "GNU Taler" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -23,6 +29,12 @@ "comment" : "Bundle name", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "GNU_Taler" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -41,6 +53,12 @@ "comment" : "Privacy - Camera Usage Description", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "QR Codes scannen" + } + }, "en" : { "stringUnit" : { "state" : "translated", @@ -55,10 +73,40 @@ } } }, + "NSFaceIDUsageDescription" : { + "comment" : "Privacy - Face ID Usage Description", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schützen Sie Ihr Geld" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Protect your money" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proteja su dinero" + } + } + } + }, "NSHumanReadableCopyright" : { "comment" : "Copyright (human-readable)", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "© Taler-Systems.com" + } + }, "en" : { "stringUnit" : { "state" : "translated", diff --git a/TalerWallet1/Taler_Nightly InfoPlist.xcstrings b/TalerWallet1/Taler_Nightly InfoPlist.xcstrings @@ -70,6 +70,30 @@ } } }, + "NSFaceIDUsageDescription" : { + "comment" : "Privacy - Face ID Usage Description", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schützen Sie Ihr Geld" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Protect your money" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proteja su dinero" + } + } + } + }, "NSHumanReadableCopyright" : { "comment" : "Copyright (human-readable)", "localizations" : { diff --git a/TalerWallet1/Taler_Wallet InfoPlist.xcstrings b/TalerWallet1/Taler_Wallet InfoPlist.xcstrings @@ -73,6 +73,30 @@ } } }, + "NSFaceIDUsageDescription" : { + "comment" : "Privacy - Face ID Usage Description", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schützen Sie Ihr Geld" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Protect your money" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proteja su dinero" + } + } + } + }, "NSHumanReadableCopyright" : { "comment" : "Copyright (human-readable)", "extractionState" : "extracted_with_value", diff --git a/TalerWallet1/Views/Main/MainView.swift b/TalerWallet1/Views/Main/MainView.swift @@ -26,11 +26,13 @@ struct MainView: View { @EnvironmentObject private var controller: Controller @EnvironmentObject private var model: WalletModel + @EnvironmentObject private var biometricService: BiometricService @AppStorage("talerFontIndex") var talerFontIndex: Int = 0 // extension mustn't define this, so it must be here @AppStorage("playSoundsI") var playSoundsI: Int = 1 // extension mustn't define this, so it must be here @AppStorage("playSoundsB") var playSoundsB: Bool = false + @AppStorage("useAuthentication") var useAuthentication: Bool = false - @StateObject var tabBarModel = TabBarModel() + @StateObject private var tabBarModel = TabBarModel() @State private var selectedBalance: Balance? = nil // gets set in TransactionsListView @State private var selectedIndex: Int? = nil // user selects savings box in OIMview @State private var urlToOpen: URL? = nil @@ -100,7 +102,42 @@ struct MainView: View { // Color.clear } } - }//.transition(.move(edge: .top)) + } + .overlay { + if useAuthentication && !biometricService.isAuthenticated { + Color.gray.opacity(0.75) + .animation(.easeInOut, value: biometricService.isAuthenticated) + if let errorMessage = biometricService.authenticationError { + Text(errorMessage) + .talerFont(.title) + .foregroundColor(.red) + .multilineTextAlignment(.center) + .padding() + .background(WalletColors().backgroundColor) + .onTapGesture { + biometricService.authenticationError = nil + biometricService.authenticateUser() + } + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + biometricService.authenticationError = nil + } + } + } else { + Text("Authenticating...") + .talerFont(.largeTitle) + .foregroundStyle(WalletColors().talerColor) + .padding() + .background(WalletColors().backgroundColor) + .onAppear { + biometricService.authenticateUser() + } + .onTapGesture { + biometricService.authenticateUser() + } + } + } + } let mainGroup = Group { // show launch animation until either ready or error diff --git a/TalerWallet1/Views/Settings/SettingsView.swift b/TalerWallet1/Views/Settings/SettingsView.swift @@ -26,6 +26,7 @@ struct SettingsView: View { @EnvironmentObject private var controller: Controller @EnvironmentObject private var model: WalletModel + @EnvironmentObject private var biometricService: BiometricService // @Environment(\.colorSchemeContrast) private var colorSchemeContrast #if DEBUG @AppStorage("developerMode") var developerMode: Bool = true @@ -43,7 +44,7 @@ struct SettingsView: View { @AppStorage("minimalistic") var minimalistic: Bool = false @AppStorage("localConsoleL") var localConsoleL: Bool = false // for Logs @AppStorage("localConsoleO") var localConsoleO: Int = 0 // for Observability - @AppStorage("oimTwoRows") var oimTwoRows: Bool = false + @AppStorage("useAuthentication") var useAuthentication: Bool = false @State private var checkDisabled = false @State private var withDrawDisabled = false @@ -120,6 +121,10 @@ struct SettingsView: View { SettingsItem(name: bankAccountsTitle, id1: "bankAccounts", description: hideDescriptions ? nil : String(localized: "Your accounts for deposit...")) {} } + SettingsToggle(name: String(localized: "Use FaceID / TouchID"), value: $useAuthentication, id1: "useFaceID", + description: minimalistic ? nil : String(localized: "Protect your money")) { + biometricService.isAuthenticated = false + } SettingsToggle(name: String(localized: "Minimalistic"), value: $minimalistic, id1: "minimal", description: hideDescriptions ? nil : String(localized: "Omit text where possible")) { hideDescriptions = minimalistic //withAnimation { hideDescriptions = minimalistic } @@ -145,10 +150,6 @@ struct SettingsView: View { consoleManager.isVisible = localConsoleO != 0 || localConsoleL consoleManager.clear() } -#if OIM - SettingsToggle(name: String(localized: "Two rows"), value: $oimTwoRows, id1: "oimTwoRows", - description: minimalistic ? nil : String(localized: "OIM denomination layout")) -#endif #if DEBUG let showDiagnostic = diagnosticModeEnabled #else