From b28f6ae68fac7ff51385caa28255a9d4fc7b8d48 Mon Sep 17 00:00:00 2001 From: dankito Date: Sun, 30 Aug 2020 16:32:05 +0200 Subject: [PATCH] Implemented SwiftUI FlickerCodeTanView; extracted ScaleImageView from ImageTanView --- .../ui/util/FlickerCodeStepsCalculator.kt | 3 +- .../BankingiOSApp.xcodeproj/project.pbxproj | 50 ++++- .../Base.lproj/Localizable.strings | 1 + .../de.lproj/Localizable.strings | 1 + .../ui/FlickerCodeAnimator.swift | 89 +++++++++ .../BankingiOSApp/ui/UIKitExtensions.swift | 24 +++ .../BankingiOSApp/ui/ViewExtensions.swift | 5 + .../ui/views/EnterTanDialog.swift | 16 +- .../ui/views/FlickerCodeStripe.swift | 69 +++++++ .../ui/views/FlickerCodeTanView.swift | 188 ++++++++++++++++++ .../BankingiOSApp/ui/views/ImageTanView.swift | 36 +--- .../ui/views/ScaleImageView.swift | 66 ++++++ .../ui/views/TanGeneratorPositionMarker.swift | 26 +++ .../ui/views/TrianglePointingDown.swift | 27 +++ 14 files changed, 554 insertions(+), 47 deletions(-) create mode 100644 ui/BankingiOSApp/BankingiOSApp/ui/FlickerCodeAnimator.swift create mode 100644 ui/BankingiOSApp/BankingiOSApp/ui/views/FlickerCodeStripe.swift create mode 100644 ui/BankingiOSApp/BankingiOSApp/ui/views/FlickerCodeTanView.swift create mode 100644 ui/BankingiOSApp/BankingiOSApp/ui/views/ScaleImageView.swift create mode 100644 ui/BankingiOSApp/BankingiOSApp/ui/views/TanGeneratorPositionMarker.swift create mode 100644 ui/BankingiOSApp/BankingiOSApp/ui/views/TrianglePointingDown.swift diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/util/FlickerCodeStepsCalculator.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/util/FlickerCodeStepsCalculator.kt index 7515a0d0..f05a4761 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/util/FlickerCodeStepsCalculator.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/util/FlickerCodeStepsCalculator.kt @@ -1,5 +1,6 @@ package net.dankito.banking.ui.util +import net.dankito.utils.multiplatform.Freezer import net.dankito.utils.multiplatform.ObjectHolder @@ -56,7 +57,7 @@ open class FlickerCodeStepsCalculator { steps.add(calculateStep(halfbyteid, clock, bitarray)) } while (halfbyteid.value > 0 || clock.value == Bit.Low) - return steps + return Freezer.freeze(steps) } protected open fun calculateStep(halfbyteid: ObjectHolder, clock: ObjectHolder, bitarray: List): Step { diff --git a/ui/BankingiOSApp/BankingiOSApp.xcodeproj/project.pbxproj b/ui/BankingiOSApp/BankingiOSApp.xcodeproj/project.pbxproj index 1422f051..d8e830a0 100644 --- a/ui/BankingiOSApp/BankingiOSApp.xcodeproj/project.pbxproj +++ b/ui/BankingiOSApp/BankingiOSApp.xcodeproj/project.pbxproj @@ -9,6 +9,16 @@ /* Begin PBXBuildFile section */ 3607829924E148D40098FEFE /* AdaptsToKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3607829824E148D40098FEFE /* AdaptsToKeyboard.swift */; }; 360782C124E18D5E0098FEFE /* AddAccountButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360782C024E18D5E0098FEFE /* AddAccountButtonView.swift */; }; + 360782C324E49FF70098FEFE /* ValidationLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360782C224E49FF70098FEFE /* ValidationLabel.swift */; }; + 360782C524E541970098FEFE /* ScaleImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360782C424E541970098FEFE /* ScaleImageView.swift */; }; + 360782C724E544170098FEFE /* FlickerCodeTanView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360782C624E544170098FEFE /* FlickerCodeTanView.swift */; }; + 360782CB24E5746B0098FEFE /* FlickerCodeAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360782CA24E5746A0098FEFE /* FlickerCodeAnimator.swift */; }; + 360782CD24F1A57F0098FEFE /* LabelledUIKitTextFieldWithValidationLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360782CC24F1A57E0098FEFE /* LabelledUIKitTextFieldWithValidationLabel.swift */; }; + 360782CF24F3D6610098FEFE /* InfoLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360782CE24F3D6610098FEFE /* InfoLabel.swift */; }; + 360782D124F3F4120098FEFE /* SelectorWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360782D024F3F4120098FEFE /* SelectorWrapper.swift */; }; + 360782D324F429F80098FEFE /* FlickerCodeStripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360782D224F429F70098FEFE /* FlickerCodeStripe.swift */; }; + 3608D6C224FBA9C6006C93A8 /* TrianglePointingDown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3608D6C124FBA9C6006C93A8 /* TrianglePointingDown.swift */; }; + 3608D6C624FBAB41006C93A8 /* TanGeneratorPositionMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3608D6C524FBAB41006C93A8 /* TanGeneratorPositionMarker.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 */; }; @@ -128,6 +138,16 @@ /* Begin PBXFileReference section */ 3607829824E148D40098FEFE /* AdaptsToKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptsToKeyboard.swift; sourceTree = ""; }; 360782C024E18D5E0098FEFE /* AddAccountButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountButtonView.swift; sourceTree = ""; }; + 360782C224E49FF70098FEFE /* ValidationLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidationLabel.swift; sourceTree = ""; }; + 360782C424E541970098FEFE /* ScaleImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaleImageView.swift; sourceTree = ""; }; + 360782C624E544170098FEFE /* FlickerCodeTanView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlickerCodeTanView.swift; sourceTree = ""; }; + 360782CA24E5746A0098FEFE /* FlickerCodeAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlickerCodeAnimator.swift; sourceTree = ""; }; + 360782CC24F1A57E0098FEFE /* LabelledUIKitTextFieldWithValidationLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelledUIKitTextFieldWithValidationLabel.swift; sourceTree = ""; }; + 360782CE24F3D6610098FEFE /* InfoLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoLabel.swift; sourceTree = ""; }; + 360782D024F3F4120098FEFE /* SelectorWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorWrapper.swift; sourceTree = ""; }; + 360782D224F429F70098FEFE /* FlickerCodeStripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlickerCodeStripe.swift; sourceTree = ""; }; + 3608D6C124FBA9C6006C93A8 /* TrianglePointingDown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrianglePointingDown.swift; sourceTree = ""; }; + 3608D6C524FBAB41006C93A8 /* TanGeneratorPositionMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TanGeneratorPositionMarker.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 = ""; }; @@ -403,8 +423,13 @@ 36BE06B424CF85A300CBBB68 /* AmountLabel.swift */, 36BE06C724D0DE7400CBBB68 /* UIKitTextField.swift */, 36E21EDA24DC990300649DC8 /* LabelledUIKitTextField.swift */, + 360782CE24F3D6610098FEFE /* InfoLabel.swift */, + 360782C224E49FF70098FEFE /* ValidationLabel.swift */, + 360782CC24F1A57E0098FEFE /* LabelledUIKitTextFieldWithValidationLabel.swift */, 36E21EDC24DCA89100649DC8 /* TanProcedurePicker.swift */, 3607829824E148D40098FEFE /* AdaptsToKeyboard.swift */, + 360782CA24E5746A0098FEFE /* FlickerCodeAnimator.swift */, + 360782D024F3F4120098FEFE /* SelectorWrapper.swift */, ); path = ui; sourceTree = ""; @@ -424,7 +449,10 @@ 366FA4DB24C479120094F009 /* BankInfoListItem.swift */, 366FA4DF24C4924A0094F009 /* RemitteeListItem.swift */, 366FA4E124C4ED6C0094F009 /* EnterTanDialog.swift */, + 360782C424E541970098FEFE /* ScaleImageView.swift */, 36BE064E24C9A17F00CBBB68 /* ImageTanView.swift */, + 360782C624E544170098FEFE /* FlickerCodeTanView.swift */, + 360782D224F429F70098FEFE /* FlickerCodeStripe.swift */, 36E21ED024DC540400649DC8 /* SettingsDialog.swift */, 36E21ED424DC549800649DC8 /* BankSettingsDialog.swift */, 36E21ED624DC617200649DC8 /* BankAccountSettingsDialog.swift */, @@ -435,6 +463,8 @@ 36C4009A24D2F9E4005227AD /* IconedTitleView.swift */, 36E21ECE24DA0EEE00649DC8 /* IconView.swift */, 36E21EDE24DCCC2700649DC8 /* CheckmarkListItem.swift */, + 3608D6C124FBA9C6006C93A8 /* TrianglePointingDown.swift */, + 3608D6C524FBAB41006C93A8 /* TanGeneratorPositionMarker.swift */, ); path = views; sourceTree = ""; @@ -607,6 +637,7 @@ 36E21EDB24DC990300649DC8 /* LabelledUIKitTextField.swift in Sources */, 36BE068D24CE41E700CBBB68 /* Styles.swift in Sources */, 36E21ECB24D88DF000649DC8 /* UIKitExtensions.swift in Sources */, + 360782C524E541970098FEFE /* ScaleImageView.swift in Sources */, 366FA4E224C4ED6C0094F009 /* EnterTanDialog.swift in Sources */, 36FC92DC24B3A4A0002B12E9 /* AccountsTab.swift in Sources */, 36BCF86E24BA691B005BEC29 /* DependencyInjector.swift in Sources */, @@ -614,24 +645,31 @@ 36BE06B824D077EC00CBBB68 /* SwiftBankIconFinder.swift in Sources */, 36BCF89124C25971005BEC29 /* CoreDataBankingPersistence.swift in Sources */, 36FC92A124B39A05002B12E9 /* BankingiOSApp.xcdatamodeld in Sources */, + 360782D324F429F80098FEFE /* FlickerCodeStripe.swift in Sources */, 36BE06C624D080C900CBBB68 /* FaviconType.swift in Sources */, + 360782CD24F1A57F0098FEFE /* LabelledUIKitTextFieldWithValidationLabel.swift in Sources */, 36BCF89324C25BC3005BEC29 /* Mapper.swift in Sources */, 36FC92D724B3A3BA002B12E9 /* NSUrlWebClient.swift in Sources */, + 360782D124F3F4120098FEFE /* SelectorWrapper.swift in Sources */, 36BE06B324CF133400CBBB68 /* JsonEncoderSerializer.swift in Sources */, 36BE06BA24D0783900CBBB68 /* FaviconFinder.swift in Sources */, 36BCF89524C31F02005BEC29 /* AppData.swift in Sources */, 36E21EDD24DCA89100649DC8 /* TanProcedurePicker.swift in Sources */, + 3608D6C624FBAB41006C93A8 /* TanGeneratorPositionMarker.swift in Sources */, 36BE065B24CA4B3500CBBB68 /* SelectBankDialog.swift in Sources */, 36E21ED524DC549800649DC8 /* BankSettingsDialog.swift in Sources */, 36BE068924CE288800CBBB68 /* CollapsibleText.swift in Sources */, 36BE06B524CF85A300CBBB68 /* AmountLabel.swift in Sources */, 36BCF88324C098BB005BEC29 /* BankListItem.swift in Sources */, + 360782CB24E5746B0098FEFE /* FlickerCodeAnimator.swift in Sources */, 36BCF88D24C1C1EA005BEC29 /* TransferMoneyDialog.swift in Sources */, 36E21EDF24DCCC2700649DC8 /* CheckmarkListItem.swift in Sources */, 36E7BA1424B3D05C00757859 /* ViewExtensions.swift in Sources */, 36BCF88924C0A7D7005BEC29 /* Message.swift in Sources */, 366FA4E024C4924A0094F009 /* RemitteeListItem.swift in Sources */, + 3608D6C224FBA9C6006C93A8 /* TrianglePointingDown.swift in Sources */, 36BE068B24CE3B0400CBBB68 /* SwiftExtensions.swift in Sources */, + 360782C724E544170098FEFE /* FlickerCodeTanView.swift in Sources */, 36BE065D24CB08FC00CBBB68 /* LazyView.swift in Sources */, 360782C124E18D5E0098FEFE /* AddAccountButtonView.swift in Sources */, 36BCF86C24BA5E72005BEC29 /* DispatchQueueAsyncRunner.swift in Sources */, @@ -642,12 +680,14 @@ 36BE06C424D0801A00CBBB68 /* Size.swift in Sources */, 36FC92A324B39A05002B12E9 /* ContentView.swift in Sources */, 366FA4E624C6EBF40094F009 /* EnterTanState.swift in Sources */, + 360782CF24F3D6610098FEFE /* InfoLabel.swift in Sources */, 36C4009B24D2F9E4005227AD /* IconedTitleView.swift in Sources */, 36BE065724C9E04800CBBB68 /* UIKitImageView.swift in Sources */, 36BCF88724C0A310005BEC29 /* PreviewData.swift in Sources */, 36E21ED724DC617200649DC8 /* BankAccountSettingsDialog.swift in Sources */, 366FA4DA24C472A90094F009 /* Extensions.swift in Sources */, 36BE068F24CEE1BD00CBBB68 /* AllBanksListItem.swift in Sources */, + 360782C324E49FF70098FEFE /* ValidationLabel.swift in Sources */, 36BE069124CEF52800CBBB68 /* UpdateButton.swift in Sources */, 36E21ED124DC540400649DC8 /* SettingsDialog.swift in Sources */, 366FA4DC24C479120094F009 /* BankInfoListItem.swift in Sources */, @@ -767,7 +807,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.5; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -822,7 +862,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.5; + IPHONEOS_DEPLOYMENT_TARGET = 13.6; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -849,6 +889,7 @@ "../../ui/BankingUiNativeIntegration/build/xcode-frameworks/", ); INFOPLIST_FILE = BankingiOSApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -857,7 +898,7 @@ PRODUCT_BUNDLE_IDENTIFIER = net.dankito.banking.ios.BankingiOSApp.freeprovisioning; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -878,6 +919,7 @@ "../../ui/BankingUiNativeIntegration/build/xcode-frameworks/", ); INFOPLIST_FILE = BankingiOSApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -886,7 +928,7 @@ PRODUCT_BUNDLE_IDENTIFIER = net.dankito.banking.ios.BankingiOSApp.freeprovisioning; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; diff --git a/ui/BankingiOSApp/BankingiOSApp/Base.lproj/Localizable.strings b/ui/BankingiOSApp/BankingiOSApp/Base.lproj/Localizable.strings index fc8067cf..61c0aabe 100644 --- a/ui/BankingiOSApp/BankingiOSApp/Base.lproj/Localizable.strings +++ b/ui/BankingiOSApp/BankingiOSApp/Base.lproj/Localizable.strings @@ -89,6 +89,7 @@ "Enter TAN Dialog Title" = "Enter TAN"; "TAN procedure" = "TAN procedure"; "TAN medium" = "TAN medium"; +"Tan Generator Frequency" = "Frequency"; "Size" = "Size"; "TAN hint from your bank:" = "Hint from your bank:"; "Enter TAN:" = "TAN"; diff --git a/ui/BankingiOSApp/BankingiOSApp/de.lproj/Localizable.strings b/ui/BankingiOSApp/BankingiOSApp/de.lproj/Localizable.strings index d12617d9..fce3ab0d 100644 --- a/ui/BankingiOSApp/BankingiOSApp/de.lproj/Localizable.strings +++ b/ui/BankingiOSApp/BankingiOSApp/de.lproj/Localizable.strings @@ -89,6 +89,7 @@ "Enter TAN Dialog Title" = "TAN-Abfrage"; "TAN procedure" = "TAN Verfahren"; "TAN medium" = "TAN Medium"; +"Tan Generator Frequency" = "Geschw."; "Size" = "Größe"; "TAN hint from your bank:" = "Hinweis Ihrer Bank:"; "Enter TAN:" = "TAN"; diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/FlickerCodeAnimator.swift b/ui/BankingiOSApp/BankingiOSApp/ui/FlickerCodeAnimator.swift new file mode 100644 index 00000000..c2f04b67 --- /dev/null +++ b/ui/BankingiOSApp/BankingiOSApp/ui/FlickerCodeAnimator.swift @@ -0,0 +1,89 @@ +import SwiftUI +import BankingUiSwift + + +class FlickerCodeAnimator { + + static let MinFrequency = 2 + static let MaxFrequency = 40 + static let DefaultFrequency = 30 + + private var currentFrequency: Int = DefaultFrequency + + private var isPaused = false + + private var currentStepIndex = 0 + + private var steps: [Step] = [] + + private var showStep: ((Step) -> Void)? = nil + + private var timer: Timer? = nil + + + + func animate(_ flickerCode: String, _ showStep: @escaping (Step) -> Void) { + self.stop() // stop may still running previous animation + + self.steps = FlickerCodeStepsCalculator().calculateSteps(flickerCode: flickerCode) + + self.showStep = showStep + + self.start() + } + + + private func start() { + currentStepIndex = 0 + + let interval = 1.0 / Double(currentFrequency) + + timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { timer in + self.calculateAndShowNextStep() + } + } + + private func calculateAndShowNextStep() { + if isPaused == false { + let nextStep = steps[currentStepIndex] + + self.showStep?(nextStep) + + currentStepIndex = currentStepIndex + 1 + + if (currentStepIndex >= steps.count) { + currentStepIndex = 0 // all steps shown, start again from beginning + } + } + } + + func stop() { + timer?.invalidate() + + timer = nil + } + + func pause() { + self.isPaused = true + } + + func resume() { + self.isPaused = false + } + + + func setFrequency(frequency: Int) { + if frequency >= Self.MinFrequency && frequency <= Self.MaxFrequency { + let isRunning = timer != nil + + stop() + + currentFrequency = frequency + + if isRunning { + start() + } + } + } + +} diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/UIKitExtensions.swift b/ui/BankingiOSApp/BankingiOSApp/ui/UIKitExtensions.swift index cedbd2b5..ba5b4846 100644 --- a/ui/BankingiOSApp/BankingiOSApp/ui/UIKitExtensions.swift +++ b/ui/BankingiOSApp/BankingiOSApp/ui/UIKitExtensions.swift @@ -12,3 +12,27 @@ extension UIResponder { } } + +extension UserDefaults { + + func string(forKey key: String, defaultValue: String) -> String { + return UserDefaults.standard.object(forKey: key) as? String ?? defaultValue + } + + func integer(forKey key: String, defaultValue: Int) -> Int { + return UserDefaults.standard.object(forKey: key) as? Int ?? defaultValue + } + + func float(forKey key: String, defaultValue: Float) -> Float { + return UserDefaults.standard.object(forKey: key) as? Float ?? defaultValue + } + + func double(forKey key: String, defaultValue: Double) -> Double { + return UserDefaults.standard.object(forKey: key) as? Double ?? defaultValue + } + + func bool(forKey key: String, defaultValue: Bool) -> Bool { + return UserDefaults.standard.object(forKey: key) as? Bool ?? defaultValue + } + +} diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/ViewExtensions.swift b/ui/BankingiOSApp/BankingiOSApp/ui/ViewExtensions.swift index 47220321..6e12eb5c 100644 --- a/ui/BankingiOSApp/BankingiOSApp/ui/ViewExtensions.swift +++ b/ui/BankingiOSApp/BankingiOSApp/ui/ViewExtensions.swift @@ -85,6 +85,11 @@ extension View { .foregroundColor(amountColor) } + + func turnAnimationOff() -> some View { + return self.animation(nil) + } + } diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/views/EnterTanDialog.swift b/ui/BankingiOSApp/BankingiOSApp/ui/views/EnterTanDialog.swift index 4f5cdae0..da79fc0c 100644 --- a/ui/BankingiOSApp/BankingiOSApp/ui/views/EnterTanDialog.swift +++ b/ui/BankingiOSApp/BankingiOSApp/ui/views/EnterTanDialog.swift @@ -31,9 +31,9 @@ struct EnterTanDialog: View { private var showSelectTanMediumView = false - private var showFlickerCodeTanView = false + private let flickerCodeTanChallenge: FlickerCodeTanChallenge? - private var showImageTanView = false + private let imageTanChallenge: ImageTanChallenge? @State private var enteredTan = "" @@ -54,8 +54,8 @@ struct EnterTanDialog: View { self.showSelectTanMediumView = self.customersTanMedia.count > 1 // TODO: use isOpticalTanProcedure && tanMedia.count > 1 - self.showFlickerCodeTanView = tanChallenge is FlickerCodeTanChallenge - self.showImageTanView = tanChallenge is ImageTanChallenge + self.flickerCodeTanChallenge = tanChallenge as? FlickerCodeTanChallenge + self.imageTanChallenge = tanChallenge as? ImageTanChallenge if let decodingError = (tanChallenge as? FlickerCodeTanChallenge)?.flickerCode.decodingError { showDecodingTanChallengeFailedErrorDelayed(decodingError) @@ -82,12 +82,12 @@ struct EnterTanDialog: View { } } - 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.") + flickerCodeTanChallenge.map { flickerCodeTanChallenge in + FlickerCodeTanView(flickerCodeTanChallenge) } - if showImageTanView { - ImageTanView(self.tanChallenge as! ImageTanChallenge) + imageTanChallenge.map { imageTanChallenge in + ImageTanView(imageTanChallenge) } VStack { diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/views/FlickerCodeStripe.swift b/ui/BankingiOSApp/BankingiOSApp/ui/views/FlickerCodeStripe.swift new file mode 100644 index 00000000..6e154d5e --- /dev/null +++ b/ui/BankingiOSApp/BankingiOSApp/ui/views/FlickerCodeStripe.swift @@ -0,0 +1,69 @@ +import SwiftUI + + +struct FlickerCodeStripe: View { + + private static let Height = CGFloat(100) + + + @Binding private var showBit: Bool + + @Binding private var width: CGFloat + + + init(_ showBit: Binding, _ width: Binding) { + _showBit = showBit + _width = width + } + + + var body: some View { + Rectangle() + .fill(showBit ? Color.white : Color.black) + .frame(width: width, height: Self.Height) + .turnAnimationOff() // it's very important to turn animation off otherwise stripes get displayed in gray instead of white which TAN generator doesn't recognize + } + +} + + +struct FlickerCodeBit_Previews: PreviewProvider { + + static var previews: some View { + let bitWidth: CGFloat = 40 + let spaceWidth: CGFloat = 15 + + return HStack { + Spacer() + + HStack { + FlickerCodeStripe(.constant(true), .constant(bitWidth)) + + Spacer() + .frame(width: spaceWidth) + + FlickerCodeStripe(.constant(true), .constant(bitWidth)) + + Spacer() + .frame(width: spaceWidth) + + FlickerCodeStripe(.constant(true), .constant(bitWidth)) + + Spacer() + .frame(width: spaceWidth) + + FlickerCodeStripe(.constant(false), .constant(bitWidth)) + + Spacer() + .frame(width: spaceWidth) + + FlickerCodeStripe(.constant(true), .constant(bitWidth)) + } + + Spacer() + } + .frame(width: UIScreen.main.bounds.width) + .background(Color.black) + } + +} diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/views/FlickerCodeTanView.swift b/ui/BankingiOSApp/BankingiOSApp/ui/views/FlickerCodeTanView.swift new file mode 100644 index 00000000..211aff82 --- /dev/null +++ b/ui/BankingiOSApp/BankingiOSApp/ui/views/FlickerCodeTanView.swift @@ -0,0 +1,188 @@ +import SwiftUI +import BankingUiSwift + + +struct FlickerCodeTanView: View { + + private static let FlickerCodeScaleFactorUserDefaultsKey = "FlickerCodeScaleFactor" + private static let FlickerCodeFrequencyDefaultsKey = "FlickerCodeFrequency" + + private static let SpaceBetweenStripesStepSize: CGFloat = 0.5 + private static let StripesWidthStepSize: CGFloat = 2.35 * Self.SpaceBetweenStripesStepSize + + + private let MinScaleFactor: CGFloat = 10 + private let MaxScaleFactor: CGFloat + + + private var tanChallenge: BankingUiSwift.FlickerCodeTanChallenge + + @State private var showBit1: Bool = true + @State private var showBit2: Bool = true + @State private var showBit3: Bool = true + @State private var showBit4: Bool = true + @State private var showBit5: Bool = true + + private let animator: FlickerCodeAnimator = FlickerCodeAnimator() + + private let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect() + + + @State private var frequency = CGFloat(UserDefaults.standard.float(forKey: Self.FlickerCodeFrequencyDefaultsKey, defaultValue: Float(FlickerCodeAnimator.DefaultFrequency))) + + private var frequencyBinding: Binding { + Binding( + get: { self.frequency }, + set: { + if (self.frequency != $0) { + let newFrequency = $0 + + self.animator.setFrequency(frequency: Int(newFrequency)) + + UserDefaults.standard.set(newFrequency, forKey: Self.FlickerCodeFrequencyDefaultsKey) + + DispatchQueue.main.async { + self.frequency = newFrequency + } + } + }) + } + + + @State private var stripeWidth: CGFloat = 24 + @State private var spaceBetweenStripes: CGFloat = 10 + @State private var spaceBetweenTanGeneratorPositionMarker: CGFloat = 4 * 40 + 4 * 15 - TanGeneratorPositionMarker.Width + + @State private var scaleFactor = CGFloat(UserDefaults.standard.float(forKey: Self.FlickerCodeScaleFactorUserDefaultsKey, defaultValue: 20.0)) + + private var scaleFactorBinding: Binding { + Binding( + get: { self.scaleFactor }, + set: { + if (self.scaleFactor != $0) { + let newFlickerScaleFactor = $0 + + UserDefaults.standard.set(newFlickerScaleFactor, forKey: Self.FlickerCodeScaleFactorUserDefaultsKey) + + DispatchQueue.main.async { + self.scaleFactor = newFlickerScaleFactor + + self.calculateStripeWidth() + } + } + }) + } + + + init(_ tanChallenge: BankingUiSwift.FlickerCodeTanChallenge) { + self.tanChallenge = tanChallenge + + let oneStepDiff = 5 * Self.StripesWidthStepSize + 4 * Self.SpaceBetweenStripesStepSize + MaxScaleFactor = CGFloat(Int(UIScreen.main.bounds.width / oneStepDiff)) + + animator.setFrequency(frequency: Int(frequency)) + } + + + var body: some View { + Section { + HStack { + Text("Tan Generator Frequency") + + Spacer() + + Image(systemName: "tortoise") + + Slider(value: frequencyBinding, in: CGFloat(FlickerCodeAnimator.MinFrequency)...CGFloat(FlickerCodeAnimator.MaxFrequency), step: 1) + + Image(systemName: "hare") + } + + ScaleImageView(scaleFactorBinding, imageMinWidth: MinScaleFactor, imageMaxWidth: MaxScaleFactor, step: 1) + + VStack { + HStack { + Spacer() + + TanGeneratorPositionMarker() + + Spacer() + .frame(width: spaceBetweenTanGeneratorPositionMarker) + + TanGeneratorPositionMarker() + + Spacer() + } + .padding(.bottom, -4) + + HStack { + Spacer() + + HStack { + FlickerCodeStripe($showBit1, $stripeWidth) + + Spacer() + .frame(width: spaceBetweenStripes) + + FlickerCodeStripe($showBit2, $stripeWidth) + + Spacer() + .frame(width: spaceBetweenStripes) + + FlickerCodeStripe($showBit3, $stripeWidth) + + Spacer() + .frame(width: spaceBetweenStripes) + + FlickerCodeStripe($showBit4, $stripeWidth) + + Spacer() + .frame(width: spaceBetweenStripes) + + FlickerCodeStripe($showBit5, $stripeWidth) + } + + Spacer() + } + .padding(.bottom, 12) + } + .background(Color.black) + .listRowInsets(EdgeInsets()) + } + // what a hack to be able to call animator.animate() (otherwise compiler would throw 'use of immutable self in closure' error) + .onReceive(timer) { timer in + self.timer.upstream.connect().cancel() + + self.calculateStripeWidth() + + self.animator.animate(self.tanChallenge.flickerCode.parsedDataSet, self.showStep) + } + } + + + private func showStep(_ step: Step) { + self.showBit1 = step.bit1.isHigh + self.showBit2 = step.bit2.isHigh + self.showBit3 = step.bit3.isHigh + self.showBit4 = step.bit4.isHigh + self.showBit5 = step.bit5.isHigh + } + + + private func calculateStripeWidth() { + stripeWidth = scaleFactor * Self.StripesWidthStepSize + spaceBetweenStripes = scaleFactor * Self.SpaceBetweenStripesStepSize + + spaceBetweenTanGeneratorPositionMarker = 4 * stripeWidth + 4 * spaceBetweenStripes - TanGeneratorPositionMarker.Width + } + +} + + +struct FlickerCodeTanView_Previews: PreviewProvider { + + static var previews: some View { + FlickerCodeTanView(previewFlickerCodeTanChallenge) + } + +} diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/views/ImageTanView.swift b/ui/BankingiOSApp/BankingiOSApp/ui/views/ImageTanView.swift index e7ad9f20..5ba64832 100644 --- a/ui/BankingiOSApp/BankingiOSApp/ui/views/ImageTanView.swift +++ b/ui/BankingiOSApp/BankingiOSApp/ui/views/ImageTanView.swift @@ -8,51 +8,19 @@ struct ImageTanView: View { private var imageData: Data - @State private var imageWidth: CGFloat - - private let imageMinWidth: CGFloat - - private let imageMaxWidth: CGFloat - - private let step: CGFloat + @State private var imageWidth: CGFloat = UIScreen.main.bounds.width / 2 init(_ tanChallenge: ImageTanChallenge) { self.tanChallenge = tanChallenge self.imageData = tanChallenge.image.imageBytesAsNSData() - - let screenWidth = UIScreen.main.bounds.width - let screenWidthQuarter = screenWidth / 4 - - self.imageMinWidth = screenWidthQuarter < 150 ? 150 : screenWidthQuarter // don't know whey but iOS seems that it doesn't scale image smaller than 150 - self.imageMaxWidth = screenWidth - - let range = imageMaxWidth - imageMinWidth - - self._imageWidth = State(initialValue: imageMinWidth + range / 2) - - self.step = range / 20 } var body: some View { Section { - HStack { - Text("Size") - - Spacer() - - Rectangle() - .fill(Color.gray) - .frame(width: 6, height: 9) - - Slider(value: $imageWidth, in: imageMinWidth...imageMaxWidth, step: step) - - Rectangle() - .fill(Color.gray) - .frame(width: 16, height: 19) - } + ScaleImageView($imageWidth) HStack { Spacer() diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/views/ScaleImageView.swift b/ui/BankingiOSApp/BankingiOSApp/ui/views/ScaleImageView.swift new file mode 100644 index 00000000..1d242bf9 --- /dev/null +++ b/ui/BankingiOSApp/BankingiOSApp/ui/views/ScaleImageView.swift @@ -0,0 +1,66 @@ +import SwiftUI + + +struct ScaleImageView: View { + + @Binding private var imageWidth: CGFloat + + private let imageMinWidth: CGFloat + + private let imageMaxWidth: CGFloat + + private let step: CGFloat + + + init(_ imageWidth: Binding, _ initialImageWidth: CGFloat? = nil) { + let screenWidth = UIScreen.main.bounds.width + let screenWidthQuarter = screenWidth / 4 + + let imageMinWidth = screenWidthQuarter < 150 ? 150 : screenWidthQuarter // don't know whey but iOS seems that it doesn't scale image smaller than 150 + let imageMaxWidth = screenWidth + + let range = imageMaxWidth - imageMinWidth + + self.init(imageWidth, imageMinWidth: imageMinWidth, imageMaxWidth: imageMaxWidth, step: range / 20) + + self.$imageWidth.wrappedValue = initialImageWidth ?? (imageMinWidth + range / 2) + } + + init(_ imageWidth: Binding, imageMinWidth: CGFloat, imageMaxWidth: CGFloat, step: CGFloat) { + _imageWidth = imageWidth + + self.imageMinWidth = imageMinWidth + self.imageMaxWidth = imageMaxWidth + + self.step = step + } + + + var body: some View { + HStack { + Text("Size") + + Spacer() + + Rectangle() + .fill(Color.gray) // TODO: use a system color + .frame(width: 6, height: 9) + + Slider(value: $imageWidth, in: imageMinWidth...imageMaxWidth, step: step) + + Rectangle() + .fill(Color.gray) // TODO: use a system color + .frame(width: 16, height: 19) + } + } + +} + + +struct ScaleImageView_Previews: PreviewProvider { + + static var previews: some View { + ScaleImageView(.constant(250)) + } + +} diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/views/TanGeneratorPositionMarker.swift b/ui/BankingiOSApp/BankingiOSApp/ui/views/TanGeneratorPositionMarker.swift new file mode 100644 index 00000000..9a8bbb72 --- /dev/null +++ b/ui/BankingiOSApp/BankingiOSApp/ui/views/TanGeneratorPositionMarker.swift @@ -0,0 +1,26 @@ +import SwiftUI + + +struct TanGeneratorPositionMarker: View { + + static let Width = CGFloat(24) + + static let Height = CGFloat(24) + + + var body: some View { + TrianglePointingDown() + .fill(Color.gray) + .frame(width: Self.Width, height: Self.Height) + } + +} + + +struct TanGeneratorPositionMarker_Previews: PreviewProvider { + + static var previews: some View { + TanGeneratorPositionMarker() + } + +} diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/views/TrianglePointingDown.swift b/ui/BankingiOSApp/BankingiOSApp/ui/views/TrianglePointingDown.swift new file mode 100644 index 00000000..e4586c09 --- /dev/null +++ b/ui/BankingiOSApp/BankingiOSApp/ui/views/TrianglePointingDown.swift @@ -0,0 +1,27 @@ +import SwiftUI + + +struct TrianglePointingDown: Shape { + + func path(in rect: CGRect) -> Path { + var path = Path() + + path.move(to: CGPoint(x: rect.minX, y: rect.minY)) + path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY)) + path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.minX, y: rect.minY)) + + return path + } + +} + + +struct TrianglePointingDown_Previews: PreviewProvider { + + static var previews: some View { + TrianglePointingDown() + .frame(width: 300, height: 300) + } + +}