diff --git a/ui/BankingiOSApp/BankingiOSApp.xcodeproj/project.pbxproj b/ui/BankingiOSApp/BankingiOSApp.xcodeproj/project.pbxproj index 39663a30..959e78f5 100644 --- a/ui/BankingiOSApp/BankingiOSApp.xcodeproj/project.pbxproj +++ b/ui/BankingiOSApp/BankingiOSApp.xcodeproj/project.pbxproj @@ -7,6 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 366FA4D824C46B160094F009 /* AutoCompleteList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4D724C46B160094F009 /* AutoCompleteList.swift */; }; + 366FA4DA24C472A90094F009 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4D924C472A90094F009 /* Extensions.swift */; }; + 366FA4DC24C479120094F009 /* BankInfoListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4DB24C479120094F009 /* BankInfoListItem.swift */; }; + 366FA4E024C4924A0094F009 /* RemitteeListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4DF24C4924A0094F009 /* RemitteeListItem.swift */; }; + 366FA4E224C4ED6C0094F009 /* EnterTanDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4E124C4ED6C0094F009 /* EnterTanDialog.swift */; }; + 366FA4E424C4F2C70094F009 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4E324C4F2C70094F009 /* AppState.swift */; }; + 366FA4E624C6EBF40094F009 /* EnterTanState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4E524C6EBF40094F009 /* EnterTanState.swift */; }; 36BCF85424BA0C54005BEC29 /* BankList.json in Resources */ = {isa = PBXBuildFile; fileRef = 36BCF85324BA0C54005BEC29 /* BankList.json */; }; 36BCF85824BA4274005BEC29 /* BankingUiCommon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36BCF85524BA41EE005BEC29 /* BankingUiCommon.framework */; }; 36BCF85924BA4274005BEC29 /* BankingUiCommon.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 36BCF85524BA41EE005BEC29 /* BankingUiCommon.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -32,6 +39,9 @@ 36BCF89124C25971005BEC29 /* CoreDataBankingPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BCF89024C25971005BEC29 /* CoreDataBankingPersistence.swift */; }; 36BCF89324C25BC3005BEC29 /* Mapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BCF89224C25BC3005BEC29 /* Mapper.swift */; }; 36BCF89524C31F02005BEC29 /* AppData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BCF89424C31F02005BEC29 /* AppData.swift */; }; + 36BE064F24C9A17F00CBBB68 /* ImageTanView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BE064E24C9A17F00CBBB68 /* ImageTanView.swift */; }; + 36BE065724C9E04800CBBB68 /* UIKitImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BE065624C9E04800CBBB68 /* UIKitImageView.swift */; }; + 36BE065924CA3CAB00CBBB68 /* UIKitSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36BE065824CA3CAB00CBBB68 /* UIKitSearchBar.swift */; }; 36E7BA1424B3D05C00757859 /* ViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E7BA1324B3D05C00757859 /* ViewExtensions.swift */; }; 36FC929C24B39A05002B12E9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FC929B24B39A05002B12E9 /* AppDelegate.swift */; }; 36FC929E24B39A05002B12E9 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FC929D24B39A05002B12E9 /* SceneDelegate.swift */; }; @@ -87,6 +97,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 366FA4D724C46B160094F009 /* AutoCompleteList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCompleteList.swift; sourceTree = ""; }; + 366FA4D924C472A90094F009 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + 366FA4DB24C479120094F009 /* BankInfoListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankInfoListItem.swift; sourceTree = ""; }; + 366FA4DF24C4924A0094F009 /* RemitteeListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemitteeListItem.swift; sourceTree = ""; }; + 366FA4E124C4ED6C0094F009 /* EnterTanDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterTanDialog.swift; sourceTree = ""; }; + 366FA4E324C4F2C70094F009 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; + 366FA4E524C6EBF40094F009 /* EnterTanState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterTanState.swift; sourceTree = ""; }; 36BCF85324BA0C54005BEC29 /* BankList.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = BankList.json; path = ../../../tools/BankFinder/src/commonMain/resources/BankList.json; sourceTree = ""; }; 36BCF85524BA41EE005BEC29 /* BankingUiCommon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BankingUiCommon.framework; path = "../BankingUiCommon/build/xcode-frameworks/BankingUiCommon.framework"; sourceTree = ""; }; 36BCF85D24BA4DA8005BEC29 /* MultiplatformUtils.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MultiplatformUtils.framework; path = "../../common/build/xcode-frameworks/MultiplatformUtils.framework"; sourceTree = ""; }; @@ -107,6 +124,9 @@ 36BCF89024C25971005BEC29 /* CoreDataBankingPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataBankingPersistence.swift; sourceTree = ""; }; 36BCF89224C25BC3005BEC29 /* Mapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mapper.swift; sourceTree = ""; }; 36BCF89424C31F02005BEC29 /* AppData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppData.swift; sourceTree = ""; }; + 36BE064E24C9A17F00CBBB68 /* ImageTanView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTanView.swift; sourceTree = ""; }; + 36BE065624C9E04800CBBB68 /* UIKitImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitImageView.swift; sourceTree = ""; }; + 36BE065824CA3CAB00CBBB68 /* UIKitSearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitSearchBar.swift; sourceTree = ""; }; 36E7BA1324B3D05C00757859 /* ViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtensions.swift; sourceTree = ""; }; 36E7BA1824B9E70C00757859 /* xcode-frameworks */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "xcode-frameworks"; path = "../../tools/BankFinder/build/xcode-frameworks"; sourceTree = ""; }; 36FC929824B39A05002B12E9 /* BankingiOSApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BankingiOSApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -169,6 +189,9 @@ 36BCF89024C25971005BEC29 /* CoreDataBankingPersistence.swift */, 36BCF89224C25BC3005BEC29 /* Mapper.swift */, 36BCF89424C31F02005BEC29 /* AppData.swift */, + 366FA4E324C4F2C70094F009 /* AppState.swift */, + 366FA4D924C472A90094F009 /* Extensions.swift */, + 366FA4E524C6EBF40094F009 /* EnterTanState.swift */, ); path = persistence; sourceTree = ""; @@ -274,6 +297,7 @@ 36BCF86D24BA691B005BEC29 /* DependencyInjector.swift */, 36BCF88824C0A7D7005BEC29 /* Message.swift */, 36BCF88E24C1DFF7005BEC29 /* SheetPresenter.swift */, + 366FA4D724C46B160094F009 /* AutoCompleteList.swift */, ); path = ui; sourceTree = ""; @@ -287,6 +311,12 @@ 36BCF88424C098C8005BEC29 /* BankAccountListItem.swift */, 36BCF88A24C0BD2D005BEC29 /* AccountTransactionsDialog.swift */, 36BCF88C24C1C1EA005BEC29 /* TransferMoneyDialog.swift */, + 366FA4DB24C479120094F009 /* BankInfoListItem.swift */, + 366FA4DF24C4924A0094F009 /* RemitteeListItem.swift */, + 366FA4E124C4ED6C0094F009 /* EnterTanDialog.swift */, + 36BE064E24C9A17F00CBBB68 /* ImageTanView.swift */, + 36BE065624C9E04800CBBB68 /* UIKitImageView.swift */, + 36BE065824CA3CAB00CBBB68 /* UIKitSearchBar.swift */, ); path = views; sourceTree = ""; @@ -425,7 +455,7 @@ /* Begin PBXShellScriptBuildPhase section */ 36FC92D324B39E61002B12E9 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; + buildActionMask = 12; files = ( ); inputFileListPaths = ( @@ -438,7 +468,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\n#cd \"../../common/build/xcode-frameworks/\"\n#./gradlew :common:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}\n\n#cd \"../../fints4k/build/xcode-frameworks/\"\n#./gradlew :fints4k:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}#\n\n#cd \"../../tools/BankFinder/build/xcode-frameworks/\"\n#./gradlew :BankFinder:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}\n\ncd \"../../ui/BankingUiNativeIntegration/build/xcode-frameworks/\"\n./gradlew :BankingUiNativeIntegration:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}\n"; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\n#cd \"../../common/build/xcode-frameworks/\"\n#./gradlew :common:packForXcode -PXCODE_CONFIGURATION=${CONFIGURATION}\n\n#cd \"../../fints4k/build/xcode-frameworks/\"\n#./gradlew :fints4k:packForXcode -PXCODE_CONFIGURATION=${CONFIGURATION}#\n\n#cd \"../../tools/BankFinder/build/xcode-frameworks/\"\n#./gradlew :BankFinder:packForXcode -PXCODE_CONFIGURATION=${CONFIGURATION}\n\ncd \"../../ui/BankingUiNativeIntegration/build/xcode-frameworks/\"\n./gradlew :BankingUiNativeIntegration:packForXcode -PXCODE_CONFIGURATION=${CONFIGURATION}\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -447,6 +477,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 36BE065924CA3CAB00CBBB68 /* UIKitSearchBar.swift in Sources */, + 36BE064F24C9A17F00CBBB68 /* ImageTanView.swift in Sources */, + 366FA4E224C4ED6C0094F009 /* EnterTanDialog.swift in Sources */, 36FC92DC24B3A4A0002B12E9 /* AccountsTab.swift in Sources */, 36BCF86E24BA691B005BEC29 /* DependencyInjector.swift in Sources */, 36BCF89124C25971005BEC29 /* CoreDataBankingPersistence.swift in Sources */, @@ -458,6 +491,7 @@ 36BCF88D24C1C1EA005BEC29 /* TransferMoneyDialog.swift in Sources */, 36E7BA1424B3D05C00757859 /* ViewExtensions.swift in Sources */, 36BCF88924C0A7D7005BEC29 /* Message.swift in Sources */, + 366FA4E024C4924A0094F009 /* RemitteeListItem.swift in Sources */, 36BCF86C24BA5E72005BEC29 /* DispatchQueueAsyncRunner.swift in Sources */, 36BCF86324BA5097005BEC29 /* SwiftUiRouter.swift in Sources */, 36BCF88F24C1DFF7005BEC29 /* SheetPresenter.swift in Sources */, @@ -465,7 +499,13 @@ 36BCF88B24C0BD2D005BEC29 /* AccountTransactionsDialog.swift in Sources */, 36BCF87624BF114F005BEC29 /* UrlSessionWebClient.swift in Sources */, 36FC92A324B39A05002B12E9 /* ContentView.swift in Sources */, + 366FA4E624C6EBF40094F009 /* EnterTanState.swift in Sources */, + 36BE065724C9E04800CBBB68 /* UIKitImageView.swift in Sources */, 36BCF88724C0A310005BEC29 /* PreviewData.swift in Sources */, + 366FA4DA24C472A90094F009 /* Extensions.swift in Sources */, + 366FA4D824C46B160094F009 /* AutoCompleteList.swift in Sources */, + 366FA4DC24C479120094F009 /* BankInfoListItem.swift in Sources */, + 366FA4E424C4F2C70094F009 /* AppState.swift in Sources */, 36FC929E24B39A05002B12E9 /* SceneDelegate.swift in Sources */, 36BCF88524C098C8005BEC29 /* BankAccountListItem.swift in Sources */, 36FC92EF24B3BB81002B12E9 /* AddAccountDialog.swift in Sources */, @@ -646,6 +686,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"BankingiOSApp/Preview Content\""; + DEVELOPMENT_TEAM = 2L8U7W4R52; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( "../../common/build/xcode-frameworks/", @@ -661,7 +702,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = net.dankito.banking.ios.BankingiOSApp; + PRODUCT_BUNDLE_IDENTIFIER = net.dankito.banking.ios.BankingiOSApp.freeprovisioning; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -674,6 +715,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"BankingiOSApp/Preview Content\""; + DEVELOPMENT_TEAM = 2L8U7W4R52; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( "../../common/build/xcode-frameworks/", @@ -689,7 +731,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = net.dankito.banking.ios.BankingiOSApp; + PRODUCT_BUNDLE_IDENTIFIER = net.dankito.banking.ios.BankingiOSApp.freeprovisioning; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/ui/BankingiOSApp/BankingiOSApp/AppDelegate.swift b/ui/BankingiOSApp/BankingiOSApp/AppDelegate.swift index 2f2b5907..07d9dbef 100644 --- a/ui/BankingiOSApp/BankingiOSApp/AppDelegate.swift +++ b/ui/BankingiOSApp/BankingiOSApp/AppDelegate.swift @@ -8,23 +8,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - setupDI() - return true } - - func setupDI() { - let appDataFolder = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, .userDomainMask, true).first - ?? Bundle.main.resourceURL?.absoluteString ?? "" - - let persistence = CoreDataBankingPersistence(persistentContainer: self.persistentContainer) - - let dataFolder = URL(fileURLWithPath: "data", isDirectory: true, relativeTo: URL(fileURLWithPath: appDataFolder)) - - let presenter = BankingPresenterSwift(dataFolder: dataFolder, router: SwiftUiRouter(), webClient: UrlSessionWebClient(), persistence: persistence, asyncRunner: DispatchQueueAsyncRunner()) - - DependencyInjector.register(dependency: presenter) - } // MARK: UISceneSession Lifecycle diff --git a/ui/BankingiOSApp/BankingiOSApp/Base.lproj/Localizable.strings b/ui/BankingiOSApp/BankingiOSApp/Base.lproj/Localizable.strings index 3cdcf417..25abb1bb 100644 --- a/ui/BankingiOSApp/BankingiOSApp/Base.lproj/Localizable.strings +++ b/ui/BankingiOSApp/BankingiOSApp/Base.lproj/Localizable.strings @@ -1,5 +1,6 @@ "OK" = "OK"; +"Cancel" = "Cancel"; "Add" = "Add"; @@ -21,6 +22,7 @@ "Show transfer money dialog" = "Transfer money"; /* TransferMoneyDialog */ + "Transfer Money Dialog Title" = "Überweisung"; "Remittee Name" = "Name"; "Remittee IBAN" = "IBAN"; @@ -31,3 +33,12 @@ "Successfully transferred %@ %@ to %@." = "Successfully transferred %@ %@ to %@."; "Could not transfer %@ %@ to %@. Error: %@." = "Could not transfer %@ %@ to %@.\n\nError message from your bank:\n\n%@"; + + +/* EnterTanDialog */ + +"Enter TAN Dialog Title" = "Enter TAN"; +"TAN procedure" = "TAN procedure"; +"TAN medium" = "TAN medium"; +"TAN hint from your bank:" = "Hint from your bank:"; +"Enter TAN:" = "TAN"; diff --git a/ui/BankingiOSApp/BankingiOSApp/SceneDelegate.swift b/ui/BankingiOSApp/BankingiOSApp/SceneDelegate.swift index 4ccc3cce..63490fb2 100644 --- a/ui/BankingiOSApp/BankingiOSApp/SceneDelegate.swift +++ b/ui/BankingiOSApp/BankingiOSApp/SceneDelegate.swift @@ -1,5 +1,7 @@ import UIKit import SwiftUI +import BankingUiSwift + class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -12,16 +14,32 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). // Get the managed object context from the shared persistent container. - let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext + let appDelegate = UIApplication.shared.delegate as! AppDelegate + let context = appDelegate.persistentContainer.viewContext + + + let appDataFolder = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, .userDomainMask, true).first + ?? Bundle.main.resourceURL?.absoluteString ?? "" + + let persistence = CoreDataBankingPersistence(context: context) + + let dataFolder = URL(fileURLWithPath: "data", isDirectory: true, relativeTo: URL(fileURLWithPath: appDataFolder)) + + let presenter = BankingPresenterSwift(dataFolder: dataFolder, router: SwiftUiRouter(), webClient: UrlSessionWebClient(), persistence: persistence, remitteeSearcher: persistence, asyncRunner: DispatchQueueAsyncRunner()) + + DependencyInjector.register(dependency: persistence) + DependencyInjector.register(dependency: presenter) + // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath. // Add `@Environment(\.managedObjectContext)` in the views that will need the context. - let contentView = ContentView().environment(\.managedObjectContext, context) + let contentView = ContentView() + .environment(\.managedObjectContext, context) // Use a UIHostingController as window root view controller. if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) - window.rootViewController = UIHostingController(rootView: contentView) + window.rootViewController = UINavigationController(rootViewController: UIHostingController(rootView: contentView)) self.window = window window.makeKeyAndVisible() } diff --git a/ui/BankingiOSApp/BankingiOSApp/de.lproj/Localizable.strings b/ui/BankingiOSApp/BankingiOSApp/de.lproj/Localizable.strings index 01d6416c..c33fd161 100644 --- a/ui/BankingiOSApp/BankingiOSApp/de.lproj/Localizable.strings +++ b/ui/BankingiOSApp/BankingiOSApp/de.lproj/Localizable.strings @@ -1,5 +1,6 @@ "OK" = "OK"; +"Cancel" = "Abbrechen"; "Add" = "Hinzufügen"; @@ -21,6 +22,7 @@ "Show transfer money dialog" = "Überweisung"; /* TransferMoneyDialog */ + "Transfer Money Dialog Title" = "Überweisung"; "Remittee Name" = "Name"; "Remittee IBAN" = "IBAN"; @@ -31,3 +33,12 @@ "Successfully transferred %@ %@ to %@." = "%@ %@ wurden erfolgreich an %@ überwiesen."; "Could not transfer %@ %@ to %@. Error: %@." = "Konnte nicht %@ %@ an %@ überweisen.\n\nFehlermeldung Ihrer Bank:\n\n%@"; + + +/* EnterTanDialog */ + +"Enter TAN Dialog Title" = "TAN-Abfrage"; +"TAN procedure" = "TAN Verfahren"; +"TAN medium" = "TAN Medium"; +"TAN hint from your bank:" = "Hinweis Ihrer Bank:"; +"Enter TAN:" = "TAN"; diff --git a/ui/BankingiOSApp/BankingiOSApp/persistence/EnterTanState.swift b/ui/BankingiOSApp/BankingiOSApp/persistence/EnterTanState.swift new file mode 100644 index 00000000..ce00f270 --- /dev/null +++ b/ui/BankingiOSApp/BankingiOSApp/persistence/EnterTanState.swift @@ -0,0 +1,22 @@ +import Foundation +import BankingUiSwift + + +class EnterTanState : Identifiable { + + let id: Foundation.UUID = UUID() + + let customer: Customer + + let tanChallenge: TanChallenge + + let callback: (EnterTanResult) -> Void + + + init(_ customer: Customer, _ tanChallenge: TanChallenge, _ callback: @escaping (EnterTanResult) -> Void) { + self.customer = customer + self.tanChallenge = tanChallenge + self.callback = callback + } + +} diff --git a/ui/BankingiOSApp/BankingiOSApp/persistence/Extensions.swift b/ui/BankingiOSApp/BankingiOSApp/persistence/Extensions.swift index 8bbf929e..6f5db726 100644 --- a/ui/BankingiOSApp/BankingiOSApp/persistence/Extensions.swift +++ b/ui/BankingiOSApp/BankingiOSApp/persistence/Extensions.swift @@ -20,3 +20,10 @@ extension AccountTransaction : Identifiable { public var id: UUID { UUID() } } + + +extension TanProcedure : Identifiable { + + public var id: String { self.bankInternalProcedureCode } + +} diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/SwiftUiRouter.swift b/ui/BankingiOSApp/BankingiOSApp/ui/SwiftUiRouter.swift index 1e2e3213..1d725a92 100644 --- a/ui/BankingiOSApp/BankingiOSApp/ui/SwiftUiRouter.swift +++ b/ui/BankingiOSApp/BankingiOSApp/ui/SwiftUiRouter.swift @@ -1,3 +1,4 @@ +import SwiftUI import BankingUiSwift @@ -9,9 +10,15 @@ class SwiftUiRouter : IRouter { } func getTanFromUserFromNonUiThread(customer: Customer, tanChallenge: TanChallenge, presenter: BankingPresenter, callback: @escaping (EnterTanResult) -> Void) { - callback(EnterTanResult.Companion().userDidNotEnterTan()) + if let rootViewController = UIApplication.shared.windows.first(where: { (window) -> Bool in window.isKeyWindow})?.rootViewController as? UINavigationController { + let enterTanState = EnterTanState(customer, tanChallenge, callback) + + let enterTanDialogController = UIHostingController(rootView: EnterTanDialog(enterTanState)) + + rootViewController.pushViewController(enterTanDialogController, animated: true) + } } - + func getAtcFromUserFromNonUiThread(tanMedium: TanGeneratorTanMedium, callback: @escaping (EnterTanGeneratorAtcResult) -> Void) { callback(EnterTanGeneratorAtcResult.Companion().userDidNotEnterAtc()) } diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/ViewExtensions.swift b/ui/BankingiOSApp/BankingiOSApp/ui/ViewExtensions.swift index ca0afb2b..8eb2bb04 100644 --- a/ui/BankingiOSApp/BankingiOSApp/ui/ViewExtensions.swift +++ b/ui/BankingiOSApp/BankingiOSApp/ui/ViewExtensions.swift @@ -15,6 +15,23 @@ extension View { .navigationBarTitle(title, displayMode: displayMode) } + func customNavigationBarBackButton(onBackButtonPressed: @escaping () -> Void) -> some View { + return self + .navigationBarBackButtonHidden(true) + .navigationBarItems(leading: Button(action: onBackButtonPressed) { + HStack { + Image(systemName: "chevron.left") + .font(.headline) + .padding(.horizontal, 0) + + Text("Cancel") + .padding(.leading, 0) + } + .edgesIgnoringSafeArea(.leading) + .padding(.leading, 0) + }) + } + } diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/views/AccountsTab.swift b/ui/BankingiOSApp/BankingiOSApp/ui/views/AccountsTab.swift index 87943b30..415f9dcb 100644 --- a/ui/BankingiOSApp/BankingiOSApp/ui/views/AccountsTab.swift +++ b/ui/BankingiOSApp/BankingiOSApp/ui/views/AccountsTab.swift @@ -10,27 +10,24 @@ struct AccountsTab: View { var body: some View { - NavigationView { - VStack { - if data.banks.isEmpty == false { - Form { - ForEach(data.banks) { bank in - BankListItem(bank: bank) - } + VStack { + if data.banks.isEmpty == false { + Form { + ForEach(data.banks) { bank in + BankListItem(bank: bank) } } - - Spacer() - - NavigationLink(destination: AddAccountDialog()) { - Text("Add account") - } - - Spacer() } - .navigationBarHidden(true) - .navigationBarTitle(Text("Accounts"), displayMode: .inline) + + Spacer() + + NavigationLink(destination: AddAccountDialog()) { + Text("Add account") + } + + Spacer() } + .hideNavigationBar() } } diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/views/EnterTanDialog.swift b/ui/BankingiOSApp/BankingiOSApp/ui/views/EnterTanDialog.swift new file mode 100644 index 00000000..11235ba7 --- /dev/null +++ b/ui/BankingiOSApp/BankingiOSApp/ui/views/EnterTanDialog.swift @@ -0,0 +1,149 @@ +import SwiftUI +import BankingUiSwift + + +struct EnterTanDialog: View { + + @Environment(\.presentationMode) var presentation + + + private var state: EnterTanState + + private var tanChallenge: TanChallenge + + private var customer: Customer + + @State private var selectedTanProcedureIndex: Int + private var customersTanProcdures: [TanProcedure] = [] + + @State private var selectedTanMediumIndex = 0 + private var customersTanMedia: [TanMedium] = [] + + private var showSelectTanMediumView = false + + private var showFlickerCodeTanView = false + + private var showImageTanView = false + + @State private var enteredTan = "" + + + @Inject private var presenter: BankingPresenterSwift + + + init(_ state: EnterTanState) { + self.state = state + + self.tanChallenge = state.tanChallenge + self.customer = state.customer + + self.customersTanProcdures = customer.supportedTanProcedures + _selectedTanProcedureIndex = State(initialValue: customer.supportedTanProcedures.firstIndex(of: tanChallenge.tanProcedure) ?? 0) + + self.customersTanMedia = customer.tanMediaSorted + + self.showSelectTanMediumView = true // TODO: use isOpticalTanProcedure && tanMedia.count > 1 + + self.showFlickerCodeTanView = tanChallenge is FlickerCodeTanChallenge + self.showImageTanView = tanChallenge is ImageTanChallenge + } + + + var body: some View { + Form { + Section { + Picker("TAN procedure", selection: $selectedTanProcedureIndex) { + ForEach(0 ..< self.customersTanProcdures.count) { index in + Text(self.customersTanProcdures[index].displayName) + } + } + + if showSelectTanMediumView { + Picker("TAN medium", selection: $selectedTanMediumIndex) { + ForEach(0 ..< self.customersTanMedia.count) { index in + Text(self.customersTanMedia[index].displayName) + } + } + } + } + + if showFlickerCodeTanView { + Text("Entschuldigen Sie, aber die Darstellung von Flicker Codes wird gegenwärtig noch nicht unterstützt (fauler Programmierer). Bitte wählen Sie ein anderes TAN Verfahren, im Notfall chipTAN manuell.") + } + + if showImageTanView { + ImageTanView(self.tanChallenge as! ImageTanChallenge) + } + + VStack { + HStack { + Text("TAN hint from your bank:") + Spacer() + } + + HStack { + Text(tanChallenge.messageToShowToUser) + Spacer() + } + .padding(.top, 6) + } + .padding(.vertical) + + Section { + TextField("Enter TAN:", text: $enteredTan) + } + + Section { + HStack { + Spacer() + Button(action: { self.enteringTanDone() }, + label: { Text("OK") }) + .disabled(self.enteredTan.isEmpty) + Spacer() + } + } + } + .showNavigationBarTitle("Enter TAN Dialog Title") + .customNavigationBarBackButton { + self.sendEnterTanResult(EnterTanResult.Companion().userDidNotEnterTan()) + } + } + + + func enteringTanDone() { + let companion = EnterTanResult.Companion() + let result = enteredTan.isEmpty ? companion.userDidNotEnterTan() : companion.userEnteredTan(enteredTan: enteredTan) + + sendEnterTanResult(result) + } + + func sendEnterTanResult(_ result: EnterTanResult) { + self.state.callback(result) + + // TODO: if a TAN has been entered, check result if user has made a mistake and has to re-enter TAN + self.presentation.wrappedValue.dismiss() + } +} + + +struct EnterTanDialog_Previews: PreviewProvider { + static var previews: some View { + let customer = Customer(bankCode: "", customerId: "", password: "", finTsServerAddress: "") + customer.supportedTanProcedures = [ + TanProcedure(displayName: "chipTAN optisch", type: .chiptanflickercode, bankInternalProcedureCode: ""), + TanProcedure(displayName: "chipTAN QR", type: .chiptanqrcode, bankInternalProcedureCode: ""), + TanProcedure(displayName: "Secure Super Duper Plus", type: .apptan, bankInternalProcedureCode: "") + ] + + customer.tanMedia = [ + TanMedium(displayName: "EC-Karte mit Nummer 12345678", status: .available), + TanMedium(displayName: "Handy mit Nummer 0170 / 12345678", status: .available) + ] + + let tanChallenge = TanChallenge(messageToShowToUser: "Hier ist eine Nachricht deiner Bank, die dir die Welt erklaert", tanProcedure: customer.supportedTanProcedures[0]) + + let enterTanState = EnterTanState(customer, tanChallenge, { result in }) + + return EnterTanDialog(enterTanState) + } +} diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/views/ImageTanView.swift b/ui/BankingiOSApp/BankingiOSApp/ui/views/ImageTanView.swift new file mode 100644 index 00000000..e931f7af --- /dev/null +++ b/ui/BankingiOSApp/BankingiOSApp/ui/views/ImageTanView.swift @@ -0,0 +1,33 @@ +import SwiftUI +import BankingUiSwift + + +struct ImageTanView: View { + + private var tanChallenge: ImageTanChallenge + + private var imageData: Data + + + init(_ tanChallenge: ImageTanChallenge) { + self.tanChallenge = tanChallenge + + self.imageData = tanChallenge.image.imageBytesAsNSData() + } + + + var body: some View { + UIKitImageView(data: self.imageData) + } +} + + +struct ImageTanView_Previews: PreviewProvider { + + static var previews: some View { + let tanChallenge = ImageTanChallenge(image: TanImage(mimeType: "image/png", imageBytes: KotlinByteArray(size: 0), decodingError: nil), messageToShowToUser: "", tanProcedure: TanProcedure(displayName: "", type: .phototan, bankInternalProcedureCode: "")) + + return ImageTanView(tanChallenge) + } + +} diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/views/UIKitImageView.swift b/ui/BankingiOSApp/BankingiOSApp/ui/views/UIKitImageView.swift new file mode 100644 index 00000000..fa499919 --- /dev/null +++ b/ui/BankingiOSApp/BankingiOSApp/ui/views/UIKitImageView.swift @@ -0,0 +1,24 @@ +import UIKit +import SwiftUI + + +struct UIKitImageView : UIViewRepresentable { + + typealias UIViewType = UIImageView + + let data: Data + + + func makeUIView(context: Context) -> UIImageView { + let imageView = UIImageView(image: UIImage(data: data)) + + imageView.contentMode = .scaleAspectFit + + return imageView + } + + func updateUIView(_ uiView: UIImageView, context: Context) { + + } + +}