diff --git a/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/PaymentViewController.swift b/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/PaymentViewController.swift
index be0178720..296c2cc6f 100644
--- a/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/PaymentViewController.swift
+++ b/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/PaymentViewController.swift
@@ -119,6 +119,15 @@ class PaymentViewController: UIViewController {
field.attributedPlaceholder = NSAttributedString(string: placeholderText)
}
payButton.isEnabled = false
+
+ let payButtonTitle = NSLocalizedString("ginipaybank.reviewscreen.payButton.title",
+ comment: "Pay button")
+ payButton.setTitle(payButtonTitle, for: .normal)
+
+ let backToBusinessButtonTitle = NSLocalizedString("ginipaybank.reviewscreen.backToInsurance.title",
+ comment: "Back to insurance")
+ backToBusinessButton.setTitle(backToBusinessButtonTitle, for: .normal)
+
backToBusinessButton.isHidden = true
}
diff --git a/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/de.lproj/Localizable.strings b/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/de.lproj/Localizable.strings
index 2c47eb89d..a712c954b 100644
--- a/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/de.lproj/Localizable.strings
+++ b/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/de.lproj/Localizable.strings
@@ -18,3 +18,6 @@
"ginipaybank.errors.failed.amount.non.empty.check" = "Summe kann nicht 0 sein";
"ginipaybank.errors.failed.purpose.non.empty.check" = "Verwendungszweck ist notwendig.";
"ginipaybank.errors.failed.default.textfield.validation.check" = "Das Feld ist ungültig";
+
+"ginipaybank.reviewscreen.payButton.title" = "Zahlen";
+"ginipaybank.reviewscreen.backToInsurance.title" = "Zur Health App";
diff --git a/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/en.lproj/Localizable.strings b/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/en.lproj/Localizable.strings
index 6c3ba3e4a..4a5364b7b 100644
--- a/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/en.lproj/Localizable.strings
+++ b/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/en.lproj/Localizable.strings
@@ -17,3 +17,6 @@
"ginipaybank.errors.failed.amount.non.empty.check" = "Amount can't be 0";
"ginipaybank.errors.failed.purpose.non.empty.check" = "Purpose is required.";
"ginipaybank.errors.failed.default.textfield.validation.check" = "The field is not valid";
+
+"ginipaybank.reviewscreen.payButton.title" = "Pay";
+"ginipaybank.reviewscreen.backToInsurance.title" = "Return to Health app";
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Documentation/sections/Documentation.md b/HealthAPILibrary/GiniHealthAPILibrary/Documentation/sections/Documentation.md
index ca2890f1a..834d11207 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Documentation/sections/Documentation.md
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Documentation/sections/Documentation.md
@@ -23,8 +23,8 @@ Further documentation with information about how install and integrate it can be
## Requirements
-- iOS 11+
-- Xcode 12+
+- iOS 12+
+- Xcode 15+
## Author
@@ -34,4 +34,4 @@ Gini GmbH, hello@gini.net
The Gini Health API Library for iOS is licensed under a Private License. See [the license](https://developer.gini.net/gini-mobile-ios/GiniHealthAPILibrary/license.html) for more info.
-**Important:** Always make sure to ship all license notices and permissions with your application.
+> ⚠️ **Important:** Always make sure to ship all license notices and permissions with your application.
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Documentation/source/Installation.md b/HealthAPILibrary/GiniHealthAPILibrary/Documentation/source/Installation.md
index aea305455..e3de87072 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Documentation/source/Installation.md
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Documentation/source/Installation.md
@@ -10,14 +10,14 @@ Once you have your Swift package set up, adding `GiniHealthAPILibrary` as a depe
```swift
dependencies: [
- .package(url: "https://github.com/gini/health-api-library-ios.git", .exact("3.0.1"))
+ .package(url: "https://github.com/gini/health-api-library-ios.git", .exact("4.0.0"))
]
```
In case that you want to use the certificate pinning in the library, add `GiniHealthAPILibraryPinning`:
```swift
dependencies: [
- .package(url: "https://github.com/gini/health-api-library-pinning-ios.git", .exact("3.0.1"))
+ .package(url: "https://github.com/gini/health-api-library-pinning-ios.git", .exact("4.0.0"))
]
```
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Package.swift b/HealthAPILibrary/GiniHealthAPILibrary/Package.swift
index ec74be202..f91699754 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Package.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Package.swift
@@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "GiniHealthAPILibrary",
- platforms: [.iOS(.v11)],
+ platforms: [.iOS(.v12)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/README.md b/HealthAPILibrary/GiniHealthAPILibrary/README.md
index 6cf34ed50..1056a8d1f 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/README.md
+++ b/HealthAPILibrary/GiniHealthAPILibrary/README.md
@@ -19,8 +19,8 @@ Further documentation with information about how install and integrate it can be
## Requirements
-- iOS 11+
-- Xcode 12+
+- iOS 12+
+- Xcode 15+
## Author
@@ -30,5 +30,5 @@ Gini GmbH, hello@gini.net
The Gini Health API Library for iOS is licensed under a Private License. See [the license](https://developer.gini.net/gini-mobile-ios/GiniHealthAPILibrary/license.html) for more info.
-**Important:** Always make sure to ship all license notices and permissions with your application.
+> ⚠️ **Important:** Always make sure to ship all license notices and permissions with your application.
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/Logger.swift b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/Logger.swift
index b8fcf8937..14f7197a3 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/Logger.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Core/Logger.swift
@@ -8,7 +8,7 @@
import Foundation
import os
-enum LogEvent {
+public enum LogEvent {
case error
case success
case warning
@@ -29,7 +29,7 @@ public enum LogLevel {
case debug
}
-func Log(_ message: String,
+public func Log(_ message: String,
event: LogEvent) {
guard case .debug = GiniHealthAPI.logLevel else { return }
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentProvider.swift b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentProvider.swift
index 21a0ffb35..0bba0d629 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentProvider.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentProvider.swift
@@ -2,7 +2,7 @@
// PaymentProvider.swift
// GiniHealthAPI
//
-// Created by Nadya Karaban on 15.03.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import Foundation
@@ -16,14 +16,18 @@ public struct PaymentProvider: Codable {
public var colors: ProviderColors
var minAppVersion: MinAppVersions?
public var iconData: Data
+ public var appStoreUrlIOS: String?
+ public var universalLinkIOS: String
- public init(id: String, name: String, appSchemeIOS: String, minAppVersion: MinAppVersions?, colors: ProviderColors, iconData: Data) {
+ public init(id: String, name: String, appSchemeIOS: String, minAppVersion: MinAppVersions?, colors: ProviderColors, iconData: Data, appStoreUrlIOS: String?, universalLinkIOS: String) {
self.id = id
self.name = name
self.appSchemeIOS = appSchemeIOS
self.minAppVersion = minAppVersion
self.colors = colors
self.iconData = iconData
+ self.appStoreUrlIOS = appStoreUrlIOS
+ self.universalLinkIOS = universalLinkIOS
}
}
public typealias PaymentProviders = [PaymentProvider]
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentProviderResponse.swift b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentProviderResponse.swift
index 4c10f1658..e4794f3d3 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentProviderResponse.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentProviderResponse.swift
@@ -2,7 +2,7 @@
// PaymentProvider.swift
// GiniHealthAPI
//
-// Created by Nadya Karaban on 15.03.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import Foundation
@@ -40,13 +40,17 @@ public struct PaymentProviderResponse: Codable {
public var colors: ProviderColors
var minAppVersion: MinAppVersions?
public var iconLocation: String
+ public var appStoreUrlIOS: String?
+ public var universalLinkIOS: String
- public init(id: String, name: String, appSchemeIOS: String, minAppVersion: MinAppVersions?, colors: ProviderColors, iconLocation: String) {
+ public init(id: String, name: String, appSchemeIOS: String, minAppVersion: MinAppVersions?, colors: ProviderColors, iconLocation: String, appStoreUrlIOS: String?, universalLinkIOS: String) {
self.id = id
self.name = name
self.appSchemeIOS = appSchemeIOS
self.minAppVersion = minAppVersion
self.colors = colors
self.iconLocation = iconLocation
+ self.appStoreUrlIOS = appStoreUrlIOS
+ self.universalLinkIOS = universalLinkIOS
}
}
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentRequest.swift b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentRequest.swift
index 120702ac3..4bb7f893c 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentRequest.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentRequest.swift
@@ -2,7 +2,7 @@
// PaymentRequest.swift
// GiniHealthAPI
//
-// Created by Nadya Karaban on 19.03.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import Foundation
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentRequestBody.swift b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentRequestBody.swift
index 82bae65e8..972ec0334 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentRequestBody.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentRequestBody.swift
@@ -2,7 +2,7 @@
// PaymentRequestBody.swift
// GiniHealthAPI
//
-// Created by Nadya Karaban on 22.03.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import Foundation
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentService.swift b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentService.swift
index f7ec15c0f..9597f795a 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentService.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/Documents/Payments/PaymentService.swift
@@ -2,7 +2,7 @@
// PaymentService.swift
// GiniHealthAPI
//
-// Created by Nadya Karaban on 15.03.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import Foundation
@@ -181,7 +181,7 @@ extension PaymentService {
self.file(urlString: providerResponse.iconLocation) { result in
switch result {
case let .success(imageData):
- let provider = PaymentProvider(id: providerResponse.id, name: providerResponse.name, appSchemeIOS: providerResponse.appSchemeIOS, minAppVersion: providerResponse.minAppVersion, colors: providerResponse.colors, iconData: imageData)
+ let provider = PaymentProvider(id: providerResponse.id, name: providerResponse.name, appSchemeIOS: providerResponse.appSchemeIOS, minAppVersion: providerResponse.minAppVersion, colors: providerResponse.colors, iconData: imageData, appStoreUrlIOS: providerResponse.appStoreUrlIOS, universalLinkIOS: providerResponse.universalLinkIOS)
providers.append(provider)
case let .failure(error):
completion(.failure(error))
@@ -210,7 +210,7 @@ extension PaymentService {
self.file(urlString: providerResponse.iconLocation) { result in
switch result {
case let .success(imageData):
- let provider = PaymentProvider(id: providerResponse.id, name: providerResponse.name, appSchemeIOS: providerResponse.appSchemeIOS, minAppVersion: providerResponse.minAppVersion, colors: providerResponse.colors, iconData: imageData)
+ let provider = PaymentProvider(id: providerResponse.id, name: providerResponse.name, appSchemeIOS: providerResponse.appSchemeIOS, minAppVersion: providerResponse.minAppVersion, colors: providerResponse.colors, iconData: imageData, appStoreUrlIOS: providerResponse.appStoreUrlIOS, universalLinkIOS: providerResponse.universalLinkIOS)
completion(.success(provider))
case let .failure(error):
completion(.failure(error))
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/GiniHealthAPILibraryVersion.swift b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/GiniHealthAPILibraryVersion.swift
index 0c0baa74a..7b36d6489 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/GiniHealthAPILibraryVersion.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Sources/GiniHealthAPILibrary/GiniHealthAPILibraryVersion.swift
@@ -2,7 +2,7 @@
// GiniHealthAPILibraryVersion.swift
//
//
-// Created by Nadya Karaban on 15.10.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
-public let GiniHealthAPILibraryVersion = "3.0.1"
+public let GiniHealthAPILibraryVersion = "4.0.0"
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/PaymentServiceTests.swift b/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/PaymentServiceTests.swift
index 4dfb6c4c0..47e60b472 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/PaymentServiceTests.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/PaymentServiceTests.swift
@@ -2,7 +2,7 @@
// PaymentTests.swift
// GiniHealthAPI-Unit-Tests
//
-// Created by Nadya Karaban on 13.04.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import XCTest
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/PaymentTests.swift b/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/PaymentTests.swift
index 85f7cad77..0d06f3405 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/PaymentTests.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/PaymentTests.swift
@@ -2,7 +2,7 @@
// PaymentTests.swift
// GiniHealthAPI-Unit-Tests
//
-// Created by Nadya Karaban on 15.03.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import XCTest
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/Resources/provider.json b/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/Resources/provider.json
index 7bae0b94e..1e9c34416 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/Resources/provider.json
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/Resources/provider.json
@@ -11,5 +11,6 @@
"background": "009EDC",
"text": "FFFFFF"
},
- "iconLocation": "https://pay-api.gini.net/paymentProviders/b09ef70a-490f-11eb-952e-9bc6f4646c57/icon"
+ "iconLocation": "https://pay-api.gini.net/paymentProviders/b09ef70a-490f-11eb-952e-9bc6f4646c57/icon",
+ "universalLinkIOS": "ginipay-bank://"
}
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/Resources/providers.json b/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/Resources/providers.json
index a47251b09..f8251f857 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/Resources/providers.json
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/Resources/providers.json
@@ -12,7 +12,8 @@
"background": "009EDC",
"text": "FFFFFF"
},
- "iconLocation": "https://pay-api.gini.net/paymentProviders/b09ef70a-490f-11eb-952e-9bc6f4646c57/icon"
+ "iconLocation": "https://pay-api.gini.net/paymentProviders/b09ef70a-490f-11eb-952e-9bc6f4646c57/icon",
+ "universalLinkIOS": "ginipay-bank://"
},
{
"id": "dbe3a2ca-c9df-11eb-a1d8-a7efff6e88b7",
@@ -27,7 +28,8 @@
"background": "daa520",
"text": "54f1db"
},
- "iconLocation": "https://pay-api.gini.net/paymentProviders/dbe3a2ca-c9df-11eb-a1d8-a7efff6e88b7/icon"
+ "iconLocation": "https://pay-api.gini.net/paymentProviders/dbe3a2ca-c9df-11eb-a1d8-a7efff6e88b7/icon",
+ "universalLinkIOS": "ginipay-ingdiba://"
},
{
"id": "f7d06ee0-51fd-11ec-8216-97f0937beb16",
@@ -42,7 +44,8 @@
"background": "FFFFFF",
"text": "009EDC"
},
- "iconLocation": "https://pay-api.gini.net/paymentProviders/f7d06ee0-51fd-11ec-8216-97f0937beb16/icon"
+ "iconLocation": "https://pay-api.gini.net/paymentProviders/f7d06ee0-51fd-11ec-8216-97f0937beb16/icon",
+ "universalLinkIOS": "ginipay-ginibank://"
},
{
"id": "a65b0646-51fe-11ec-8736-c338396b2f09",
@@ -57,6 +60,7 @@
"background": "4DBED3",
"text": "FFFFFF"
},
- "iconLocation": "https://pay-api.gini.net/paymentProviders/a65b0646-51fe-11ec-8736-c338396b2f09/icon"
+ "iconLocation": "https://pay-api.gini.net/paymentProviders/a65b0646-51fe-11ec-8736-c338396b2f09/icon",
+ "universalLinkIOS": "ginipay-consorsbank://"
}
]
diff --git a/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/Utils.swift b/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/Utils.swift
index 7461e78ff..9500d5052 100644
--- a/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/Utils.swift
+++ b/HealthAPILibrary/GiniHealthAPILibrary/Tests/GiniHealthAPILibraryTests/Utils.swift
@@ -61,7 +61,7 @@ func loadProviders() -> PaymentProviders {
let providersResponse = try! JSONDecoder().decode([PaymentProviderResponse].self, from: jsonData!)
for providerResponse in providersResponse {
let imageData = UIImage(named: "Gini-Test-Payment-Provider", in: Bundle.module, compatibleWith: nil)?.pngData()
- let provider = PaymentProvider(id: providerResponse.id, name: providerResponse.name, appSchemeIOS: providerResponse.appSchemeIOS, minAppVersion: providerResponse.minAppVersion, colors: providerResponse.colors, iconData: imageData ?? Data())
+ let provider = PaymentProvider(id: providerResponse.id, name: providerResponse.name, appSchemeIOS: providerResponse.appSchemeIOS, minAppVersion: providerResponse.minAppVersion, colors: providerResponse.colors, iconData: imageData ?? Data(), appStoreUrlIOS: providerResponse.appStoreUrlIOS, universalLinkIOS: providerResponse.universalLinkIOS)
providers.append(provider)
}
return providers
diff --git a/HealthAPILibrary/GiniHealthAPILibraryExample/GiniHealthAPILibraryExample/AppDelegate.swift b/HealthAPILibrary/GiniHealthAPILibraryExample/GiniHealthAPILibraryExample/AppDelegate.swift
index 6af2a979c..d12d27045 100644
--- a/HealthAPILibrary/GiniHealthAPILibraryExample/GiniHealthAPILibraryExample/AppDelegate.swift
+++ b/HealthAPILibrary/GiniHealthAPILibraryExample/GiniHealthAPILibraryExample/AppDelegate.swift
@@ -2,7 +2,7 @@
// AppDelegate.swift
// HealthAPILibraryExample
//
-// Created by Nadya Karaban on 30.09.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthAPILibrary/GiniHealthAPILibraryExample/GiniHealthAPILibraryExample/SceneDelegate.swift b/HealthAPILibrary/GiniHealthAPILibraryExample/GiniHealthAPILibraryExample/SceneDelegate.swift
index 6fd494e6c..53901495c 100644
--- a/HealthAPILibrary/GiniHealthAPILibraryExample/GiniHealthAPILibraryExample/SceneDelegate.swift
+++ b/HealthAPILibrary/GiniHealthAPILibraryExample/GiniHealthAPILibraryExample/SceneDelegate.swift
@@ -2,7 +2,7 @@
// SceneDelegate.swift
// HealthAPILibraryExample
//
-// Created by Nadya Karaban on 30.09.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthAPILibrary/GiniHealthAPILibraryExample/GiniHealthAPILibraryExample/ViewController.swift b/HealthAPILibrary/GiniHealthAPILibraryExample/GiniHealthAPILibraryExample/ViewController.swift
index fe8fb5746..d13e0e466 100644
--- a/HealthAPILibrary/GiniHealthAPILibraryExample/GiniHealthAPILibraryExample/ViewController.swift
+++ b/HealthAPILibrary/GiniHealthAPILibraryExample/GiniHealthAPILibraryExample/ViewController.swift
@@ -2,7 +2,7 @@
// ViewController.swift
// HealthAPILibraryExample
//
-// Created by Nadya Karaban on 30.09.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthAPILibrary/GiniHealthAPILibraryPinning/Package-release.swift b/HealthAPILibrary/GiniHealthAPILibraryPinning/Package-release.swift
index d9e425b81..f7bfe4ef3 100644
--- a/HealthAPILibrary/GiniHealthAPILibraryPinning/Package-release.swift
+++ b/HealthAPILibrary/GiniHealthAPILibraryPinning/Package-release.swift
@@ -17,7 +17,7 @@ let package = Package(
// .package(url: /* package url */, from: "1.0.0"),
.package(name: "TrustKit", url: "https://github.com/datatheorem/TrustKit.git", from: "2.0.0"),
- .package(name: "GiniHealthAPILibrary", url: "https://github.com/gini/health-api-library-ios.git", .exact("3.0.1")),
+ .package(name: "GiniHealthAPILibrary", url: "https://github.com/gini/health-api-library-ios.git", .exact("4.0.0")),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
diff --git a/HealthAPILibrary/GiniHealthAPILibraryPinning/Package.swift b/HealthAPILibrary/GiniHealthAPILibraryPinning/Package.swift
index 1bdc1384f..d28aec8d1 100644
--- a/HealthAPILibrary/GiniHealthAPILibraryPinning/Package.swift
+++ b/HealthAPILibrary/GiniHealthAPILibraryPinning/Package.swift
@@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "GiniHealthAPILibraryPinning",
- platforms: [.iOS(.v12), .macOS(.v10_13)],
+ platforms: [.iOS(.v12)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
diff --git a/HealthAPILibrary/GiniHealthAPILibraryPinning/README.md b/HealthAPILibrary/GiniHealthAPILibraryPinning/README.md
index e55313664..cd5900500 100644
--- a/HealthAPILibrary/GiniHealthAPILibraryPinning/README.md
+++ b/HealthAPILibrary/GiniHealthAPILibraryPinning/README.md
@@ -27,5 +27,5 @@ Gini GmbH, hello@gini.net
The Gini Health API Library Pinning for iOS is licensed under a Private License. See [the license](https://developer.gini.net/gini-mobile-ios/GiniHealthAPILibrary/license.html) for more info.
-**Important:** Always make sure to ship all license notices and permissions with your application.
+> ⚠️ **Important:** Always make sure to ship all license notices and permissions with your application.
diff --git a/HealthAPILibrary/GiniHealthAPILibraryPinning/Sources/GiniHealthAPILibraryPinning/GiniHealthAPILibraryPinningVersion.swift b/HealthAPILibrary/GiniHealthAPILibraryPinning/Sources/GiniHealthAPILibraryPinning/GiniHealthAPILibraryPinningVersion.swift
index 1164ee986..31db288c0 100644
--- a/HealthAPILibrary/GiniHealthAPILibraryPinning/Sources/GiniHealthAPILibraryPinning/GiniHealthAPILibraryPinningVersion.swift
+++ b/HealthAPILibrary/GiniHealthAPILibraryPinning/Sources/GiniHealthAPILibraryPinning/GiniHealthAPILibraryPinningVersion.swift
@@ -2,7 +2,7 @@
// GiniHealthAPILibraryPinningVersion.swift
//
//
-// Created by Nadya Karaban on 15.10.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
-public let GiniHealthAPILibraryPinningVersion = "3.0.1"
+public let GiniHealthAPILibraryPinningVersion = "4.0.0"
diff --git a/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryExample/AppDelegate.swift b/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryExample/AppDelegate.swift
index 6af2a979c..d12d27045 100644
--- a/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryExample/AppDelegate.swift
+++ b/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryExample/AppDelegate.swift
@@ -2,7 +2,7 @@
// AppDelegate.swift
// HealthAPILibraryExample
//
-// Created by Nadya Karaban on 30.09.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryExample/SceneDelegate.swift b/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryExample/SceneDelegate.swift
index 6fd494e6c..53901495c 100644
--- a/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryExample/SceneDelegate.swift
+++ b/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryExample/SceneDelegate.swift
@@ -2,7 +2,7 @@
// SceneDelegate.swift
// HealthAPILibraryExample
//
-// Created by Nadya Karaban on 30.09.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryExample/ViewController.swift b/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryExample/ViewController.swift
index 97454022f..2a627c656 100644
--- a/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryExample/ViewController.swift
+++ b/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryExample/ViewController.swift
@@ -2,7 +2,7 @@
// ViewController.swift
// HealthAPILibraryExample
//
-// Created by Nadya Karaban on 30.09.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryPinningExampleTests/GiniHealthAPILibraryPinningIntegrationTests.swift b/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryPinningExampleTests/GiniHealthAPILibraryPinningIntegrationTests.swift
index 0a6c53fa9..18ce73c1a 100644
--- a/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryPinningExampleTests/GiniHealthAPILibraryPinningIntegrationTests.swift
+++ b/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryPinningExampleTests/GiniHealthAPILibraryPinningIntegrationTests.swift
@@ -2,7 +2,7 @@
// GiniHealthAPILibraryPinningIntegrationTests.swift
// GiniHealthAPILibraryPinningExampleTests
//
-// Created by Nadya Karaban on 17.05.22.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import XCTest
diff --git a/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryPinningExampleTests/GiniHealthAPILibraryPinningIntegrationWrongCertificatesTests.swift b/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryPinningExampleTests/GiniHealthAPILibraryPinningIntegrationWrongCertificatesTests.swift
index cc6ac5f32..96cf8f1b8 100644
--- a/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryPinningExampleTests/GiniHealthAPILibraryPinningIntegrationWrongCertificatesTests.swift
+++ b/HealthAPILibrary/GiniHealthAPILibraryPinningExample/GiniHealthAPILibraryPinningExampleTests/GiniHealthAPILibraryPinningIntegrationWrongCertificatesTests.swift
@@ -2,7 +2,7 @@
// GiniHealthAPILibraryPinningIntegrationWrongCertificatesTests.swift
// GiniHealthAPILibraryPinningExampleTests
//
-// Created by Nadya Karaban on 18.05.22.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import XCTest
diff --git a/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Customization guide/PaymentReview.PNG b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Customization guide/PaymentReview.PNG
deleted file mode 100644
index 55d1f51e8..000000000
Binary files a/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Customization guide/PaymentReview.PNG and /dev/null differ
diff --git a/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Customization guide/SelectionStyle.jpeg b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Customization guide/SelectionStyle.jpeg
deleted file mode 100644
index 26a30a5ed..000000000
Binary files a/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Customization guide/SelectionStyle.jpeg and /dev/null differ
diff --git a/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/BankSelectionBottomSheet.png b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/BankSelectionBottomSheet.png
new file mode 100644
index 000000000..50e5d0f32
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/BankSelectionBottomSheet.png differ
diff --git a/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/InvoiceListWithPaymentComponent.png b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/InvoiceListWithPaymentComponent.png
new file mode 100644
index 000000000..4fc0f82dc
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/InvoiceListWithPaymentComponent.png differ
diff --git a/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/PaymentFeatureInformationScreen.png b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/PaymentFeatureInformationScreen.png
new file mode 100644
index 000000000..e8074e9d9
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/PaymentFeatureInformationScreen.png differ
diff --git a/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/PaymentReviewScreen.png b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/PaymentReviewScreen.png
new file mode 100644
index 000000000..fd98ca4b1
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/PaymentReviewScreen.png differ
diff --git a/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/ReviewScreenAfterResolvingPayment.PNG b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/ReviewScreenAfterResolvingPayment.PNG
index 7b33f6e14..077b089fc 100644
Binary files a/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/ReviewScreenAfterResolvingPayment.PNG and b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/ReviewScreenAfterResolvingPayment.PNG differ
diff --git a/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/ReviewScreenBeforeResolvingPayment.PNG b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/ReviewScreenBeforeResolvingPayment.PNG
index 84aacf3f2..1192031a5 100644
Binary files a/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/ReviewScreenBeforeResolvingPayment.PNG and b/HealthSDK/GiniHealthSDK/Documentation/jazzy-theme/assets/img/Integration guide/ReviewScreenBeforeResolvingPayment.PNG differ
diff --git a/HealthSDK/GiniHealthSDK/Documentation/sections/Documentation.md b/HealthSDK/GiniHealthSDK/Documentation/sections/Documentation.md
index 0eac465d9..b62cd7b5a 100644
--- a/HealthSDK/GiniHealthSDK/Documentation/sections/Documentation.md
+++ b/HealthSDK/GiniHealthSDK/Documentation/sections/Documentation.md
@@ -15,13 +15,13 @@ The Gini Health API provides an information extraction service for analyzing hea
## Requirements
-- iOS 11.0+
-- Xcode 12.0+
+- iOS 12.0+
+- Xcode 15.3+
**Note:**
In order to have better analysis results it is highly recommended to enable only devices with 8MP camera and flash. These devices would be:
-* iPhones with iOS 11 or higher.
+* iPhones with iOS 12 or higher.
* iPad Pro devices (iPad Air 2 and iPad Mini 4 have 8MP camera but no flash).
## Author
@@ -32,4 +32,4 @@ Gini GmbH, hello@gini.net
The Gini Health SDK for iOS is licensed under a Private License. See [the license](https://developer.gini.net/gini-mobile-ios/GiniHealthSDK/license.html) for more info.
-**Important:** Always make sure to ship all license notices and permissions with your application.
+> ⚠️ **Important:** Always make sure to ship all license notices and permissions with your application.
diff --git a/HealthSDK/GiniHealthSDK/Documentation/source/Customization guide.md b/HealthSDK/GiniHealthSDK/Documentation/source/Customization guide.md
index ceb64b05f..6156ea641 100644
--- a/HealthSDK/GiniHealthSDK/Documentation/source/Customization guide.md
+++ b/HealthSDK/GiniHealthSDK/Documentation/source/Customization guide.md
@@ -3,69 +3,104 @@ Customization guide
The Gini Health SDK components can be customized either through the `GiniHealthConfiguration`, the `Localizable.string` file or through the assets. Here you can find a complete guide with the reference to every customizable item.
-- [Generic components](#generic-components)
+- [Overview of the UI customization options](#overview-of-the-ui-customization-options)
+- [Payment Component](#payment-component)
+- [Bank Selection Bottom Sheet](#bank-selection-bottom-sheet)
+- [Payment Feature Info Screen](#payment-feature-info-screen)
- [Payment Review screen](#payment-review-screen)
-## Supporting dark mode
+## Overview of the UI customization options
-Some background and text colors use the `GiniColor` type with which you can set colors for dark and light modes. The text colors should also be set in contrast to the background colors.
+### Colors
-## Generic components
+We provide a global color palette `GiniColors.xcassets` which you are free to override.
+For example, if you want to override Accent01 color you need to create an Accent01.colorset with your wished value in your main bundle.
+The custom colors are then applied to all screens.
-##### 1. Gini Capture font
+Find the names of the color resources in the color palette (you can also view it in Figma [here](https://www.figma.com/file/rnNBzzwk41f7mB6z58oqV8/iOS-Gini-Health-SDK-4.0.0-UI-Customisation?type=design&node-id=8905%3A975&mode=design&t=o5dQ7ZlNOfbapxmp-1)).
-- Font → `GiniHealthConfiguration.customFont`
+### Images
+
+Customizing of images is done via overriding of image sets.
+If you want to override specific SDK images:
+1. Create an asset catalog for images called `GiniImages.xcassets` in your app.
+2. Add your own images to `GiniImages.xcassets` using the image names from the SDK's UI customization guide. It is important to name the images you wish to override exactly as shown in the UI customization guide, otherwise overriding won’t work.
+
+### Typography
+
+We provide global typography based on text appearance styles from UIFont.TextStyle.
+Preview our typography and find the names of the style resources (you can also view it in Figma [here](https://www.figma.com/file/rnNBzzwk41f7mB6z58oqV8/iOS-Gini-Health-SDK-4.0.0-UI-Customisation?type=design&node-id=2574%3A12863&mode=design&t=o5dQ7ZlNOfbapxmp-1)).
+
+In the example below you can see to override a font for `.body1`
+
+```swift
+let configuration = GiniHealthConfiguration()
+
+configuration.updateFont(UIFont(name: "Impact", size: 15)!, for: .body1)
+health.setConfiguration(configuration)
+```
+
+### Text
+
+Text customization is done via overriding of string resources.
+For example you would like to customize pay invoice button label in the Payment Component:
+
+1. Find a string key for a text that you would like to customize.
+For the [Pay the invoice button label](https://www.figma.com/file/rnNBzzwk41f7mB6z58oqV8/iOS-Gini-Health-SDK-4.0.0-UI-Customisation?type=design&node-id=8987%3A2854&mode=design&t=o5dQ7ZlNOfbapxmp-1) in the Payment Component we use `ginihealth.paymentcomponent.payInvoice.label`.
+2. Add the string key with a desired value to `Localizable.strings` in your app.
+
+### Supporting dark mode
+
+We support dark mode in our SDK. If you decide to customize the color palette, please ensure that the text colors are also set in contrast to the background colors.
+
+## Payment Component
+
+You can also view the UI customisation guide in Figma [here](https://www.figma.com/file/rnNBzzwk41f7mB6z58oqV8/iOS-Gini-Health-SDK-4.0.0-UI-Customisation?type=design&node-id=8987%3A2854&mode=design&t=o5dQ7ZlNOfbapxmp-1).
+
+**Note:**
+To copy text from Figma you need to have a Figma account. If you don't have one, you can create one for free.
+
+
+
+## Bank Selection Bottom Sheet
+
+You can also view the UI customisation guide in Figma [here](https://www.figma.com/file/rnNBzzwk41f7mB6z58oqV8/iOS-Gini-Health-SDK-4.0.0-UI-Customisation?type=design&node-id=9008%3A1654&mode=design&t=o5dQ7ZlNOfbapxmp-1).
+
+**Note:**
+To copy text from Figma you need to have a Figma account. If you don't have one, you can create one for free.
+
+
+
+## Payment Feature Info Screen
+
+You can also view the UI customisation guide in Figma [here](https://www.figma.com/file/rnNBzzwk41f7mB6z58oqV8/iOS-Gini-Health-SDK-4.0.0-UI-Customisation?type=design&node-id=9044%3A1582&mode=design&t=o5dQ7ZlNOfbapxmp-1).
+
+**Note:**
+To copy text from Figma you need to have a Figma account. If you don't have one, you can create one for free.
+
+
## Payment Review screen
+
+You can also view the UI customisation guide in Figma [here](https://www.figma.com/file/rnNBzzwk41f7mB6z58oqV8/iOS-Gini-Health-SDK-4.0.0-UI-Customisation?type=design&node-id=9008%3A1300&mode=design&t=o5dQ7ZlNOfbapxmp-1).
+
+**Note:**
+To copy text from Figma you need to have a Figma account. If you don't have one, you can create one for free.
+
+
+
+> **Note:**
+> - PaymentReviewViewController contains the following configuration options:
+> - paymentReviewStatusBarStyle: Sets the status bar style on the payment review screen. Only if `View controller-based status bar appearance` = `YES` in `Info.plist`.
+> - showPaymentReviewCloseButton: If set to true, a floating close button will be shown in the top right corner of the screen.
+Default value is false.
+
+For enabling `showPaymentReviewCloseButton`:
+
+```swift
+let giniConfiguration = GiniHealthConfiguration()
+config.showPaymentReviewCloseButton = true
+healthSDK.setConfiguration(config)
+```
-
-
-
-
-##### 1. Background color
-- Background color → `GiniHealthConfiguration.paymentScreenBackgroundColor` using `GiniColor` with dark mode and light mode colors
-
-##### 2. Input fields container
-- Background color → `GiniHealthConfiguration.inputFieldsContainerBackgroundColor` using `GiniColor` with dark mode and light mode colors
-
-##### 3. Input field
-- Background color → `GiniHealthConfiguration.paymentInputFieldBackgroundColor` using `GiniColor` with dark mode and light mode colors
-- Text color → `GiniHealthConfiguration.paymentInputFieldTextColor` using `GiniColor` with dark mode and light mode colors
-- Font → `GiniHealthConfiguration.paymentInputFieldFont`
-- Corner radius → `GiniHealthConfiguration.paymentInputFieldCornerRadius`
-- Border width → `GiniHealthConfiguration.paymentInputFieldBorderWidth`
-
-
-
-
-- Error selection style border color and error label text color → `GiniHealthConfiguration.paymentInputFieldErrorStyleColor` using `UIColor`
-- Focus selection style border color → `GiniHealthConfiguration.paymentInputFieldSelectionStyleColor` using `UIColor`
-- Focus selection style background color → `GiniHealthConfiguration.paymentInputFieldSelectionBackgroundColor` using `UIColor`
-- Placeholder text color → `GiniHealthConfiguration.paymentInputFieldPlaceholderTextColor` using `GiniColor` with dark mode and light mode colors
-- Placeholder font → `GiniHealthConfiguration.paymentInputFieldPlaceholderFont`
-- Recipient placeholder text → *ginihealth.reviewscreen.recipient.placeholder* localized string
-- IBAN placeholder text → *ginihealth.reviewscreen.iban.placeholder* localized string
-- Amount placeholder text → *ginihealth.reviewscreen.amount.placeholder* localized string
-- Purpose placeholder text → *ginihealth.reviewscreen.usage.placeholder* localized string
-
-- Recipient error label text → *ginihealth.errors.failed.recipient.non.empty.check* localized string
-- IBAN error label text → *ginihealth.errors.failed.iban.non.empty.check* localized string
-- IBAN validation error label text → *ginihealth.errors.failed.iban.validation.check* localized string
-- Amount error label text → *ginihealth.errors.failed.amount.non.empty.check* localized string
-- Purpose error label text → *ginihealth.errors.failed.purpose.non.empty.check* localized string
-- Default validation error label text → *ginihealth.errors.failed.default.textfield.validation.check* localized string
-
-- Amount placeholder text → *ginihealth.reviewscreen.amount.placeholder* localized string
-- Purpose placeholder text → *ginihealth.reviewscreen.usage.placeholder* localized string
-
-##### 4. Pay button
-- Text color for disabled state → `GiniHealthConfiguration.payButtonDisabledTextColor` using `GiniColor` with dark mode and light mode colors
-- Background color for disabled state → `GiniHealthConfiguration.payButtonDisabledTextColor` using `GiniColor` with dark mode and light mode colors
-- Font → `GiniHealthConfiguration.payButtonTextFont`
-- Corner radius → `GiniHealthConfiguration.payButtonCornerRadius`
-
-##### 5. Loading indicator
-- Color → `GiniHealthConfiguration.loadingIndicatorColor` using `GiniColor` with dark mode and light mode colors
-- Indicator Style → `GiniHealthConfiguration.loadingIndicatorStyle` using `UIActivityIndicatorView.Style`
-- Scale factor → `GiniHealthConfiguration.loadingIndicatorScale`
\ No newline at end of file
diff --git a/HealthSDK/GiniHealthSDK/Documentation/source/Event tracking guide.md b/HealthSDK/GiniHealthSDK/Documentation/source/Event tracking guide.md
index b7fbafafa..6357d7787 100644
--- a/HealthSDK/GiniHealthSDK/Documentation/source/Event tracking guide.md
+++ b/HealthSDK/GiniHealthSDK/Documentation/source/Event tracking guide.md
@@ -4,10 +4,8 @@ Event Tracking
The Gini Health SDK has the ability to track user events. In order to receive the events, implement the `GiniHealthTrackingDelegate` protocol and supply the delegate when initializing `PaymentReviewViewController`. For example:
```swift
-let viewController = PaymentReviewViewController.instantiate(with: self.health,
- document: document,
- extractions: extractions,
- trackingDelegate: self)
+let viewController = paymentComponentsController.loadPaymentReviewScreenFor(documentID: documentId,
+ trackingDelegate: self)
```
## Events
@@ -16,7 +14,6 @@ Event types are partitioned into different domains according to the screens that
| Domain | Event type | Additional info keys | Comment |
| --- | --- | --- | --- |
-| Payment Review Screen | `onNextButtonClicked` |`"paymentProvider"`| User tapped "next" button from the payment review screen |
+| Payment Review Screen | `onToTheBankButtonClicked` |`"paymentProvider"`| User tapped "To the banking app" button from the payment review screen |
| Payment Review Screen | `onCloseButtonClicked` || User tapped "close" button and closed the payment review screen |
| Payment Review Screen | `onCloseKeyboardButtonClicked` || User tapped "close" button and keyboard will be hidden from the payment review screen |
-| Payment Review Screen | `onBankSelectionButtonClicked` |`"paymentProvider"`| User tapped on the bank selection button from the payment review screen |
diff --git a/HealthSDK/GiniHealthSDK/Documentation/source/Installation.md b/HealthSDK/GiniHealthSDK/Documentation/source/Installation.md
index 5a205be75..2bd9a9ff8 100644
--- a/HealthSDK/GiniHealthSDK/Documentation/source/Installation.md
+++ b/HealthSDK/GiniHealthSDK/Documentation/source/Installation.md
@@ -10,34 +10,13 @@ Once you have your Swift package set up, adding `GiniHealthSDK` as a dependency
```swift
dependencies: [
- .package(url: "https://github.com/gini/health-sdk-ios.git", .exact("3.0.1"))
+ .package(url: "https://github.com/gini/health-sdk-ios.git", .exact("4.0.0"))
]
```
-In case that you want to use the certificate pinning in the library, add `GiniHealthAPILibraryPinning`:
+In case that you want to use [the certificate pinning](https://www.ssl.com/blogs/what-is-certificate-pinning/#:~:text=Certificate%20pinning%20is%20a%20security,(Transport%20Layer%20Security)%20protocols.) in the library, add `GiniHealthAPILibraryPinning`:
```swift
dependencies: [
- .package(url: "https://github.com/gini/health-sdk-pinning-ios.git", .exact("3.0.1"))
+ .package(url: "https://github.com/gini/health-sdk-pinning-ios.git", .exact("4.0.0"))
]
```
-
-## Manually
-
-If you prefer not to use a dependency management tool, you can integrate the Gini Health SDK into your project manually.
-To do so drop the [GiniHealthSDK](https://github.com/gini/gini-mobile-ios/tree/main/HealthSDK/GiniHealthSDK) (classes and assets) folder into your project as well as its dependant libraries like [GiniHealthAPILibrary](https://github.com/gini/gini-mobile-ios/tree/main/HealthAPILibrary/GiniHealthAPILibrary) and add the files to your target.
-
-For manual installation [GiniHealthSDKPinning](https://github.com/gini/gini-mobile-ios/tree/main/HealthSDK/GiniHealthSDKPinning) you need to copy the sources and assets of its dependencies:
- - [GiniHealthAPILibrary](https://github.com/gini/gini-mobile-ios/tree/main/HealthAPILibrary/GiniHealthAPILibrary)
- - [GiniHealthAPILibraryPinning](https://github.com/gini/gini-mobile-ios/tree/main/HealthAPILibrary/GiniHealthAPILibraryPinning)
- - [GiniHealthSDK](https://github.com/gini/gini-mobile-ios/tree/main/HealthSDK/GiniHealthSDK)
-
-You can find the list of dependencies [here](https://github.com/gini/gini-mobile-ios/blob/main/HealthSDK/GiniHealthSDKPinning/Package.swift).
-
-Xcode will automatically check your project for swift files and will create an autogenerated import header for you.
-Use this header in an Objective-C project by adding
-
-```Obj-C
-#import "YourProjectName-Swift.h"
-```
-
-to your implementation or header files. Note that spaces in your project name result in underscores. So `Your Project` becomes `Your_Project-Swift.h`.
diff --git a/HealthSDK/GiniHealthSDK/Documentation/source/Integration.md b/HealthSDK/GiniHealthSDK/Documentation/source/Integration.md
index 289d2492a..fe0a60890 100644
--- a/HealthSDK/GiniHealthSDK/Documentation/source/Integration.md
+++ b/HealthSDK/GiniHealthSDK/Documentation/source/Integration.md
@@ -5,21 +5,21 @@ The Gini Health SDK for iOS provides all the UI and functionality needed to use
The Gini Health API provides an information extraction service for analyzing health invoices. Specifically, it extracts information such as the document sender or the payment relevant information (amount to pay, IBAN, etc.). In addition it also provides a secure channel for sharing payment related information between clients.
-**Note** For supporting each payment provider you need to specify `LSApplicationQueriesSchemes` in your `Info.plist` file. App schemes for specification will be provided by Gini.
-
-
-## Upload the document
-
-Document upload can be done in two ways:
-
-using `GiniHealthAPILibrary`
-using `GiniCapture`
+> ⚠️ **Important:**
+For supporting each payment provider you need to specify `LSApplicationQueriesSchemes` in your `Info.plist` file. App schemes for specification will be provided by Gini.
## GiniHealthAPI initialization
+> ⚠️ **Important:**
You should have received Gini Health API client credentials from us. Please get in touch with us in case you don't have them.
+You can easy initialize `GiniHealthAPI` with the client credentials:
+
+```swift
+let apiLib = GiniHealthAPI.Builder(client: client).build()
+```
+
If you want to use a transparent proxy with your own authentication you can specify your own domain and add `AlternativeTokenSource` protocol implementation:
```swift
@@ -29,11 +29,11 @@ If you want to use a transparent proxy with your own authentication you can spec
```
The token your provide will be added as a bearer token to all api.custom.net requests.
-## Certificate pinning
+## Certificate pinning (optional)
If you want to use _Certificate pinning_, provide metadata for the upload process, you can pass both your public key pinning configuration (see [TrustKit repo](https://github.com/datatheorem/TrustKit) for more information)
```swift
- let giniApiLib = GiniHealthAPI
+ let apiLib = GiniHealthAPI
.Builder(client: Client(id: "your-id",
secret: "your-secret",
domain: "your-domain"),
@@ -41,17 +41,17 @@ If you want to use _Certificate pinning_, provide metadata for the upload proces
pinningConfig: yourPublicPinningConfig)
.build()
```
-> ⚠️ **Important**
-> - The document metadata for the upload process is intended to be used for reporting.
## GiniHealth initialization
-Now that the `GiniHealthAPI` has been initialized, you can initialize `GiniHealth`
+Now that the `GiniHealthAPI` has been initialized, you can initialize `GiniHealth`:
```swift
let healthSDK = GiniHealth(with: giniApiLib)
```
-and upload your document if you plan to do it with `GiniHealth`. First you need get document service and create partial document.
+## Document upload
+
+For the document upload if you plan to do it with `GiniHealth`. First you need get document service and create partial document.
```swift
let documentService: DefaultDocumentService = healthSDK.documentService()
@@ -66,7 +66,7 @@ After receiving the partial document in completion you can get actual composite
```swift
let partialDocs = [PartialDocumentInfo(document: createdDocument.links.document)]
- self.healthSDK.documentService
+self.healthSDK.documentService
.createDocument(fileName: "ginipay-composite",
docType: nil,
type: .composite(CompositeDocumentInfo(partialDocuments: partialDocs)),
@@ -74,39 +74,165 @@ let partialDocs = [PartialDocumentInfo(document: createdDocument.links.document)
```
-## Check preconditions
+## Check which documents/invoices are payable
+
+GiniHealth provides a method for checking if the document is payable or not.
+
+```swift
+healthSDK.checkIfDocumentIsPayable(docId: String,
+ completion: @escaping (Result) -> Void)
+```
+
+The method returns success and `true` value if Iban was extracted.
+
+> - We recommend using a `DispatchGroup` for these requests, waiting till all of them are ready, and then, reloading the list.
+
+```swift
+for giniDocument in dataDocuments {
+ dispatchGroup.enter()
+ self.paymentComponentsController.checkIfDocumentIsPayable(docId: createdDocument.id, completion: { [weak self] result in
+ switch result {
+ // ...
+ }
+ self?.dispatchGroup.leave()
+ })
+}
+dispatchGroup.notify(queue: .main) {
+ // Reload List
+}
+```
+
+## Integrate the Payment component
+
+We provide a custom payment component view to help users pay the invoice/document.
+Please follow the steps below for the payment component integration.
+
+### 1. Create an instance of the `PaymentComponentsController`.
+
+```swift
+let paymentComponentsController = PaymentComponentsController(giniHealth: health)
+```
+
+### 2. Load the payment providers
+
+You will load the list of the payment providers by calling the `loadPaymentProviders` function from the `PaymentComponentsController` and conform to the `PaymentComponentsControllerProtocol`.
+
+```swift
+paymentComponentsController.delegate = self // where self is your viewController
+paymentComponentsController.loadPaymentProviders()
+```
+
+* `PaymentComponentsControllerProtocol` provides information when the `PaymentComponentsController` is loading.
+You can show/hide an `UIActivityIndicator` based on that.
+
+* `PaymentComponentsControllerProtocol` provides completion handlers when `PaymentComponentsController` fetched successfully payment providers or when it failed with an error.
-There are three methods in GiniHealth:
+> **Note:**
+It should be sufficient to call paymentComponentsController.loadPaymentProviderApps() only once when your app starts.
-* `healthSDK.isAnyBankingAppInstalled(appSchemes: [String])` without a networking call, returns true when at least the one of the listed among `LSApplicationQueriesSchemes` in your `Info.plist` is installed on the device and can support Gini Pay functionality,
-* `healthSDK.checkIfAnyPaymentProviderAvailable()` with a networking call, returns a list of availible payment provider or informs that there are no supported banking apps installed,
-* `healthSDK.checkIfDocumentIsPayable(docId: String)` returns true if Iban was extracted.
+> - We effectively handle situations where there are no payment providers available.
+> - Based on the payment provider's colors, the `UIView` will automatically change its color.
-## Fetching data for payment review screen
+### 3. Show the Payment Component view
-If the preconditions checks are succeeded you can fetch the document and extractions for Payment Review screen:
+In this step you will show a payment component view and conform to the `PaymentComponentViewProtocol`.
+
+Depending on the value of `isPayable`, incorporate the corresponding payment component view into your cells using this function:
```swift
-healthSDK.fetchDataForReview(documentId: documentId,
- completion: @escaping (Result) -> Void)
+public func paymentView(documentId: String) -> UIView
```
-The method above returns the completion block with the struct `DataForReview`, which includes document and extractions.
-## Payment review screen initialization
+> - We suggest placing this `UIView` within a vertical `UIStackView`. Additionally, in the `prepareForReuse()` function of each cell, remove the payment component view if it exists.
+> - Furthermore, employing automatic dimension height in the `UITableView` containing the cells is recommended.
+
+* `PaymentComponentViewProtocol` is the view protocol and provides events handlers when the user tapped on various areas on the payment component view (more information icon, bank/payment provider picker, the pay invoice button and etc.).
+
+> - Make sure you properly link `PaymentComponentsControllerProtocol` and `PaymentComponentViewProtocol` delegates to get notified.
+
+## Show PaymentInfoViewController
+
+The `PaymentInfoViewController` displays information and an FAQ section about the payment feature.
+It requires a `PaymentComponentsController` instance (see `Integrate the Payment component` step 1).
+
+> **Note:**
+> - The `PaymentInfoViewController` can be presented modally, used in a container view or pushed to a navigation view controller. Make sure to add your own navigation around the provided views.
+
+> ⚠️ **Important:**
+> - The `PaymentInfoViewController` presentation should happen in `func didTapOnMoreInformation(documentId: String?)` inside
+`PaymentComponentViewProtocol` implementation.(`Integrate the Payment component` step 3).
+
+```swift
+func didTapOnMoreInformation(documentId: String?) {
+ let paymentInfoViewController = paymentComponentsController.paymentInfoViewController()
+ self.yourInvoicesListViewController.navigationController?.pushViewController(paymentInfoViewController,
+ animated: true)
+}
+ ```
+
+## Show BankSelectionBottomSheet
+
+The `BankSelectionBottomSheet` displays a list of available banks for the user to choose from.
+If a banking app is not installed it will also display its AppStore link.
+The `BankSelectionBottomSheet` presentation requires a `PaymentComponentsController` instance from the `Integrate the Payment component` step 1.
+
+> **Note:**
+> - We strongly recommend to present `BankSelectionBottomSheet` modally with a `.overFullScreen` presentation style.
+
+> ⚠️ **Important:**
+> - The `BankSelectionBottomSheet` presentation should happen in `func didTapOnBankPicker(documentId: String?)` inside
+`PaymentComponentViewProtocol` implementation (see `Integrate the Payment component` step 3).
```swift
-let vc = PaymentReviewViewController.instantiate(with giniHealth: healthSDK,
- data: dataForReview)
+func didTapOnBankPicker(documentId: String?) {
+ let bankSelectionBottomSheet = paymentComponentsController.bankSelectionBottomSheet()
+ bankSelectionBottomSheet.modalPresentationStyle = .overFullScreen
+ self.yourInvoicesListViewController.present(bankSelectionBottomSheet,
+ animated: true)
+ }
+ ```
+
+## Show PaymentReviewViewController
+
+The `PaymentReviewViewController` displays an invoice's pages and extractions. It also lets users pay the invoice with the bank they selected in the `BankSelectionBottomSheet`.
+
+The `PaymentReviewViewController` presentation requires a `PaymentComponentsController` instance from the `Integrate the Payment component` step 1 and `documentId`.
+
+> **Note:**
+> - The `PaymentReviewViewController` can be presented modally, used in a container view or pushed to a navigation view controller. Make sure to add your own navigation around the provided views.
+
+> ⚠️ **Important:**
+> - The `PaymentReviewViewController` presentation should happen in `func didTapOnBankPicker(documentId: String?)` inside
+`PaymentComponentViewProtocol` implementation (see `Integrate the Payment component` step 3).
+
+```swift
+ func didTapOnPayInvoice(documentId: String?) {
+ guard let documentId else { return }
+ paymentComponentsController.loadPaymentReviewScreenFor(documentID: documentId, trackingDelegate: self) { [weak self] viewController, error in
+ if let error {
+ self?.showErrorsIfAny()
+ } else if let viewController {
+ viewController.modalTransitionStyle = .coverVertical
+ viewController.modalPresentationStyle = .overCurrentContext
+ self?.yourInvoicesListViewController.present(viewController, animated: true)
+ }
+ }
+ }
```
-The screen can be presented modally, used in a container view or pushed to a navigation view controller. Make sure to add your own navigational elements around the provided views.
-To also use the `GiniHealthConfiguration`:
+> **Note:**
+> - PaymentReviewViewController contains the following configuration options:
+> - paymentReviewStatusBarStyle: Sets the status bar style on the payment review screen. Only if `View controller-based status bar appearance` = `YES` in `Info.plist`.
+> - showPaymentReviewCloseButton: If set to true, a floating close button will be shown in the top right corner of the screen.
+Default value is false.
+
+For enabling `showPaymentReviewCloseButton`:
```swift
let giniConfiguration = GiniHealthConfiguration()
-config.loadingIndicatorColor = .black
+config.showPaymentReviewCloseButton = true
.
.
.
healthSDK.setConfiguration(config)
-```
+```
\ No newline at end of file
diff --git a/HealthSDK/GiniHealthSDK/Documentation/source/License.md b/HealthSDK/GiniHealthSDK/Documentation/source/License.md
index 1b42eb828..03a96f55b 100644
--- a/HealthSDK/GiniHealthSDK/Documentation/source/License.md
+++ b/HealthSDK/GiniHealthSDK/Documentation/source/License.md
@@ -4,7 +4,7 @@ Always make sure to ship all license notices and permissions with your applicati
## The Gini Health SDK for iOS is licensed under a Private License.
- Copyright (c) 2014-2022, Gini GmbH
+ Copyright (c) 2024, Gini GmbH
All rights reserved.
The Gini Health SDK is licensed through Gini GmbH ("Gini") and may not be
@@ -19,7 +19,7 @@ Always make sure to ship all license notices and permissions with your applicati
## The Gini Health SDK Pinning for iOS is licensed under a Private License.
- Copyright (c) 2014-2022, Gini GmbH
+ Copyright (c) 2024, Gini GmbH
All rights reserved.
The Gini Health SDK Pinning is licensed through Gini GmbH ("Gini") and may not be
diff --git a/HealthSDK/GiniHealthSDK/Documentation/source/Migration guide.md b/HealthSDK/GiniHealthSDK/Documentation/source/Migration guide.md
deleted file mode 100644
index 8ded7092e..000000000
--- a/HealthSDK/GiniHealthSDK/Documentation/source/Migration guide.md
+++ /dev/null
@@ -1,21 +0,0 @@
-Migrate from the Gini Pay Business SDK
-=======================================
-
-We've migrated from CocoaPods to Swift Package Manager. Please, find more details in [Installation](https://developer.gini.net/gini-mobile-ios/GiniHealthSDK/installation.html).
-
-Update classes
-
-Replace `GiniApiLib` with `GiniHealthAPI`.
-Replace `GiniPayBusiness` with `GiniHealth`.
-Replace `GiniPayBusinessConfiguration` with `GiniHealthConfiguration`.
-Replace `GiniPayBusinessDelegate` with `GiniHealthDelegate`.
-Replace `GiniPayBusinessError` with `GiniHealthError`.
-
-Localizable strings
-
-Replace `ginipaybusiness.*` with `ginihealth.*`.
-
-Migrate from Pay API Library to Health API Library
-===================================================
-
-See the Health API Library's [migration guide](https://developer.gini.net/gini-mobile-ios/GiniHealthAPILibrary/migration-guide.html).
\ No newline at end of file
diff --git a/HealthSDK/GiniHealthSDK/Documentation/source/Testing.md b/HealthSDK/GiniHealthSDK/Documentation/source/Testing.md
index 0fb552539..fd93ccdb0 100644
--- a/HealthSDK/GiniHealthSDK/Documentation/source/Testing.md
+++ b/HealthSDK/GiniHealthSDK/Documentation/source/Testing.md
@@ -5,9 +5,11 @@ Testing
In order for banking apps to be able to return the user to your app after the payment has been resolved you need register a scheme for your app to respond to a deep link scheme known by the Gini Bank API.
+> **Info:**
You should already have a scheme and host from us. Please contact us in case you don't have them.
-The following is an example for the deep link gini-pay://payment-requester:
+The following is an example for the deep link `gini-pay://payment-requester`:
+
@@ -19,33 +21,66 @@ An example banking app is available in the [Gini Mobile Monorepo iOS](https://gi
In order to test using our example banking app you need to use development client credentials. This will make sure
the Gini Health SDK uses a test payment provider which will open our example banking app. To inject your API credentials into the Bank example app you need to fill in your credentials in [Credentials.plist](https://github.com/gini/gini-mobile-ios/blob/main/BankSDK/GiniBankSDKExample/GiniBankSDKExampleBank/Credentials.plist).
-#### End to end testing
+### End to end testing
The app scheme in our banking example app: `ginipay-bank://`. Please, specify this scheme `LSApplicationQueriesSchemes` in your app in `Info.plist` file.
-After you've set the client credentials in the example banking app and installed it on your device you can run your app
-and verify that `healthSDK.isAnyBankingAppInstalled(appSchemes: [String])` returns true and check other preconditions.
+After you've set the client credentials in the example banking app and installed it on your device you can run your app.
+
+#### Payment component
+
+After following the integration steps above you'll arrive at the `Payment Invoice list screen`, which already has integrated the `Payment Component`.
+The following screenshot shows a sample list of invoices where the `PaymentComponent` is shown for each invoice.
+
+
+
+
+
+#### Bank Selection Bottom sheet
+
+You should see the `Gini-Test-Payment-Provider` preselected in every payment component view. By clicking the picker you should see the `BankSelectionBottomSheet` with the list of available banking apps (including `Gini-Test-Payment-Provider` and other testing and production apps).
+
+
+
+
+
+#### More information and FAQ
+
+By clicking either the more information or the info icon on the `Payment Component` view you should see the `Payment feature Info screen` with information about the payment feature and an FAQ section.
+
+
+
+
+
+#### Payment Review
-After following the integration steps above you'll arrive at the payment review screen.
+By clicking the `Pay the invoice` button on a `Payment Component` view you should see the `Payment Review screen`, which shows the invoice's pages and the payment information. It also allows editing the payment information. The `To the banking app` button should have the icon and colors of the banking app, which was selected in the payment component view.
Check that the extractions and the document preview are shown and then press the `Pay` button:
-
+
+#### Execute payment
+
+When clicking the `To the banking app` button on the payment review you should be redirected to the example banking app where the payment information will be fetched from Gini (including any changes you made on the payment review). Press the "Pay" button to execute a test payment which will mark the payment as paid in the [Gini Health API](https://health-api.gini.net/documentation/#gini-health-api-documentation).
You should be redirected to the example banking app where the final extractions are shown:
-
+
After you press the `Pay` button the Gini Bank SDK resolves the payment and allows you to return to your app:
-
+
+#### Return to your app
+
+After the test payment has been executed, the example banking app should show a "Return to Health app" button which should take you back to your app.
+
For handling incoming url in your app after redirecting back from the banking app you need to implement to handle the incoming url:
The following is an example for the url `gini-pay://payment-requester`:
@@ -63,7 +98,7 @@ The following is an example for the url `gini-pay://payment-requester`:
With these steps completed you have verified that your app, the Gini Health API, the Gini Health SDK and the Gini
Bank SDK work together correctly.
-#### Testing in production
+### Testing in production
The steps are the same but instead of the development client credentials you will need to use production client
credentials. This will make sure the Gini Health SDK receives real payment providers which open real banking apps.
diff --git a/HealthSDK/GiniHealthSDK/Package-release.swift b/HealthSDK/GiniHealthSDK/Package-release.swift
index 836f91cbc..5d642cfb3 100644
--- a/HealthSDK/GiniHealthSDK/Package-release.swift
+++ b/HealthSDK/GiniHealthSDK/Package-release.swift
@@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "GiniHealthSDK",
defaultLocalization: "en",
- platforms: [.iOS(.v11)],
+ platforms: [.iOS(.v12)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
@@ -16,7 +16,7 @@ let package = Package(
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
- .package(name: "GiniHealthAPILibrary", url: "https://github.com/gini/health-api-library-ios.git", .exact("3.0.1")),
+ .package(name: "GiniHealthAPILibrary", url: "https://github.com/gini/health-api-library-ios.git", .exact("4.0.0")),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
diff --git a/HealthSDK/GiniHealthSDK/Package.swift b/HealthSDK/GiniHealthSDK/Package.swift
index 2e79908da..4fbfd6c86 100644
--- a/HealthSDK/GiniHealthSDK/Package.swift
+++ b/HealthSDK/GiniHealthSDK/Package.swift
@@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "GiniHealthSDK",
defaultLocalization: "en",
- platforms: [.iOS(.v11)],
+ platforms: [.iOS(.v12)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
diff --git a/HealthSDK/GiniHealthSDK/README.md b/HealthSDK/GiniHealthSDK/README.md
index c48944ea0..90af0107e 100644
--- a/HealthSDK/GiniHealthSDK/README.md
+++ b/HealthSDK/GiniHealthSDK/README.md
@@ -28,13 +28,13 @@ To inject your API credentials into the Health and Bank example apps you need to
## Requirements
-- iOS 11+
-- Xcode 12+
+- iOS 12+
+- Xcode 15+
**Note:**
In order to have better analysis results it is highly recommended to enable only devices with 8MP camera and flash. These devices would be:
-* iPhones with iOS 11 or higher.
+* iPhones with iOS 12 or higher.
* iPad Pro devices (iPad Air 2 and iPad Mini 4 have 8MP camera but no flash).
## Author
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/BankProviderViewController.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/BankProviderViewController.swift
deleted file mode 100644
index 4a22194be..000000000
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/BankProviderViewController.swift
+++ /dev/null
@@ -1,173 +0,0 @@
-//
-// BankProviderSelectionViewController.swift
-//
-//
-// Created by Nadya Karaban on 01.12.21.
-//
-
-import GiniHealthAPILibrary
-import UIKit
-
-class BankProviderViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
- let defaults = UserDefaults.standard
- @IBOutlet var backgroundView: UIView!
-
- @IBOutlet var containerView: UIView!
- @IBOutlet var titleLabel: UILabel!
- @IBOutlet var scrollDownIndicatorView: UIView!
-
- @IBOutlet var providersTableView: SelfSizingTableView!
- private var viewTranslation = CGPoint(x: 0, y: 0)
- private let cellIdentifier = "bankTableViewCellIdentifier"
- private let defaultProviderIdKey = "ginihealth.defaultPaymentProviderId"
-
- private var giniHealthConfiguration = GiniHealthConfiguration.shared
-
- private var model = BankProviderViewModel()
- private var selectedProvider: PaymentProvider?
- var onSelectedProviderDidChanged: (_ provider: PaymentProvider) -> Void = { _ in }
-
- override func viewDidLoad() {
- super.viewDidLoad()
- setupViewModel()
- configureUI()
- }
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- animatePopupView()
- }
-
- func setupViewModel() {
- model.onBankSelection = { [weak self] provider in
- DispatchQueue.main.async {
- self?.saveDefaultPaymentProvider(provider: provider)
- }
- }
- model.reloadTableViewClosure = { [weak self] () in
- DispatchQueue.main.async {
- self?.providersTableView.reloadData()
- }
- }
- selectedProvider = self.fetchDefaultPaymentProvider()
- }
-
- func configureUI() {
- backgroundView.backgroundColor = UIColor.from(giniColor: giniHealthConfiguration.bankSelectionDimmedOverlayBackgroundColor)
- containerView.roundCorners(corners: [.topLeft, .topRight], radius: 12)
- containerView.backgroundColor = UIColor.from(giniColor: giniHealthConfiguration.bankSelectionScreenBackgroundColor)
- scrollDownIndicatorView.backgroundColor = UIColor.from(giniColor: giniHealthConfiguration.bankSelectionScrollDownIndicatorViewColor)
-
- titleLabel.font = giniHealthConfiguration.customFont.with(weight: .bold, size: 17, style: .caption1)
- titleLabel.textColor = UIColor.from(giniColor: giniHealthConfiguration.bankSelectionTitleTextColor)
- titleLabel.text = NSLocalizedStringPreferredFormat("ginihealth.bankprovidersscreen.title",
- comment: "title for bank providers view")
- providersTableView.backgroundView?.backgroundColor = UIColor.from(giniColor: giniHealthConfiguration.bankSelectionScreenBackgroundColor)
- providersTableView.separatorColor = UIColor.from(giniColor: giniHealthConfiguration.bankSelectionCellSeparatorColor)
-
- providersTableView.reloadData()
-
- containerView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handleDismiss)))
- }
-
- fileprivate func animateSlideDownViewAndDismiss() {
- let screenSize = UIScreen.main.bounds.size
- UIView.animate(withDuration: 0.5,
- delay: 0, usingSpringWithDamping: 1.0,
- initialSpringVelocity: 1.0,
- options: [], animations: {
- self.backgroundView.alpha = 0
- self.containerView.frame = CGRect(x: 0, y: screenSize.height, width: screenSize.width, height: self.containerView.frame.height)
- }, completion: { _ in
- self.dismiss(animated: false, completion: nil)
- })
- }
-
- @objc func handleDismiss(sender: UIPanGestureRecognizer) {
- viewTranslation = sender.translation(in: view)
- animateSlideDownViewAndDismiss()
- }
-
- func animatePopupView() {
- let screenSize = UIScreen.main.bounds.size
- UIView.animate(withDuration: 0.5,
- delay: 0, usingSpringWithDamping: 1.0,
- initialSpringVelocity: 1.0,
- options: [], animations: {
- self.containerView.frame = CGRect(x: 0, y: screenSize.height - self.containerView.frame.height, width: screenSize.width, height: self.containerView.frame.height)
- }, completion: nil)
- }
-
- override public func touchesBegan(_ touches: Set, with event: UIEvent?) {
- let touch = touches.first
- if let touchView = self.containerView, touch?.view != touchView {
- animateSlideDownViewAndDismiss()
- }
- }
-
- public static func instantiate(with providers: PaymentProviders) -> BankProviderViewController {
- let vc = (UIStoryboard(name: "BankSelection", bundle: giniHealthBundle())
- .instantiateViewController(withIdentifier: "bankSelectionViewController") as? BankProviderViewController)!
- vc.model.providers = providers
- return vc
- }
-
- func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- model.providers.count
- }
-
- func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier,
- for: indexPath) as! BankTableViewCell
- cell.viewModel = model.getCellViewModel(at: indexPath)
- return cell
- }
-
- func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
- if let index = model.providers.firstIndex(where: { $0.id == selectedProvider?.id }), index == indexPath.row {
- (cell as! BankTableViewCell).setSelected(true, animated: true)
- let indexPathToSelect = NSIndexPath(row: indexPath.row, section: 0)
- tableView.selectRow(at: indexPathToSelect as IndexPath, animated: true, scrollPosition: .none)
- }
- }
-
-
- func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
- return 60
- }
-
- func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
- if let header = view as? UITableViewHeaderFooterView {
- header.backgroundView?.backgroundColor = nil
- }
- }
-
- func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- let newProvider = model.providers[indexPath.row]
- model.onBankSelection(newProvider)
- self.onSelectedProviderDidChanged(newProvider)
- self.animateSlideDownViewAndDismiss()
- }
-
- func saveDefaultPaymentProvider(provider: PaymentProvider){
- defaults.set(provider.id, forKey: defaultProviderIdKey)
- }
-
- func fetchDefaultPaymentProvider() -> PaymentProvider {
- let providerId = defaults.string(forKey: defaultProviderIdKey)
- return model.providers.first(where: { $0.id == providerId }) ?? model.providers[0]
- }
-}
-
-class SelfSizingTableView: UITableView {
- override var contentSize: CGSize {
- didSet {
- invalidateIntrinsicContentSize()
- setNeedsLayout()
- }
- }
-
- override var intrinsicContentSize: CGSize {
- let height = min(.infinity, contentSize.height)
- return CGSize(width: contentSize.width, height: height)
- }
-}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/BankProviderViewModel.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/BankProviderViewModel.swift
deleted file mode 100644
index 30d8a2756..000000000
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/BankProviderViewModel.swift
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-// File.swift
-//
-//
-// Created by Nadya Karaban on 02.12.21.
-//
-
-import Foundation
-
-import GiniHealthAPILibrary
-import UIKit
-/**
- View model class for bank selection screen
- */
-public class BankProviderViewModel: NSObject {
-
- var onBankSelection: (_ provider: PaymentProvider) -> Void = { _ in }
- var reloadTableViewClosure: () -> Void = {}
-
- var providers: PaymentProviders = [] {
- didSet {
- var vms = [BankTableViewCellViewModel]()
- for provider in providers {
- let vm = BankTableViewCellViewModel(paymentProvider: provider)
- vms.append(vm)
- }
- cellViewModels.append(contentsOf: vms)
- }
- }
-
- var selectedPaymentProvider: PaymentProvider? {
- didSet {
- if let provider = selectedPaymentProvider {
- self.onBankSelection(provider)
- }
- }
- }
-
- private var cellViewModels = [BankTableViewCellViewModel]() {
- didSet {
- self.reloadTableViewClosure()
- }
- }
-
- var numberOfCells: Int {
- return cellViewModels.count
- }
-
- func getCellViewModel(at indexPath: IndexPath) -> BankTableViewCellViewModel {
- return cellViewModels[indexPath.row]
- }
-
-}
-
-struct BankTableViewCellViewModel {
- let name: String?
- let icon: UIImage
-
- init(paymentProvider: PaymentProvider){
- name = paymentProvider.name
- let imageData = paymentProvider.iconData
- if let image = UIImage(data: imageData){
- icon = image
- } else {
- icon = UIImage()
- }
- }
-}
-
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/BankTableViewCell.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/BankTableViewCell.swift
deleted file mode 100644
index c4d2a67ab..000000000
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/BankTableViewCell.swift
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// BankTableViewCell.swift
-//
-//
-// Created by Nadya Karaban on 13.12.21.
-//
-
-import UIKit
-class BankTableViewCell: UITableViewCell {
- @IBOutlet var bankIcon: UIImageView!
- @IBOutlet var bankName: UILabel!
- @IBOutlet var selectionIndicator: UIImageView!
-
- var viewModel: BankTableViewCellViewModel? {
- didSet {
- contentView.backgroundColor = UIColor.from(giniColor: GiniHealthConfiguration.shared.bankSelectionScreenBackgroundColor)
- bankName?.text = viewModel?.name
- bankName?.textColor = UIColor.from(giniColor: GiniHealthConfiguration.shared.bankSelectionCellTextColor)
- bankName?.font = GiniHealthConfiguration.shared.customFont.regular
- bankIcon?.image = viewModel?.icon
- bankIcon.layer.cornerRadius = GiniHealthConfiguration.shared.bankSelectionCellIconCornerRadius
- }
- }
-
- override func awakeFromNib() {
- super.awakeFromNib()
- }
-
- override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
- super.init(style: style, reuseIdentifier: reuseIdentifier)
- }
-
- required init?(coder: NSCoder) {
- super.init(coder: coder)
- }
-
- override func setSelected(_ selected: Bool, animated: Bool) {
- super.setSelected(selected, animated: animated)
- if(selected) {
- selectionIndicator.image = UIImageNamedPreferred(named: "selectionIndicator")
- } else {
- selectionIndicator.image = nil
- }
- }
-}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/ButtonConfiguration.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/ButtonConfiguration.swift
new file mode 100644
index 000000000..764f6201b
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/ButtonConfiguration.swift
@@ -0,0 +1,48 @@
+//
+// ButtonConfiguration.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+public struct ButtonConfiguration {
+ let backgroundColor: UIColor
+ let borderColor: UIColor
+ let titleColor: UIColor
+ let shadowColor: UIColor
+ let cornerRadius: CGFloat
+ let borderWidth: CGFloat
+ let shadowRadius: CGFloat
+
+ let withBlurEffect: Bool
+
+ /// Button configuration initalizer
+ /// - Parameters:
+ /// - backgroundColor: the button's background color
+ /// - borderColor: the button's border color
+ /// - titleColor: the button's title color
+ /// - shadowColor: the button's color of the shadow
+ /// - cornerRadius: the button's corner radius
+ /// - borderWidth: the button's border width
+ /// - shadowRadius: the button's shadow radius
+ /// - withBlurEffect: adds a blur effect on the button ignoring the background color and making it translucent
+ public init(backgroundColor: UIColor,
+ borderColor: UIColor,
+ titleColor: UIColor,
+ shadowColor: UIColor,
+ cornerRadius: CGFloat,
+ borderWidth: CGFloat,
+ shadowRadius: CGFloat,
+ withBlurEffect: Bool) {
+ self.backgroundColor = backgroundColor
+ self.borderColor = borderColor
+ self.titleColor = titleColor
+ self.shadowColor = shadowColor
+ self.cornerRadius = cornerRadius
+ self.borderWidth = borderWidth
+ self.shadowRadius = shadowRadius
+ self.withBlurEffect = withBlurEffect
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/CollectionFlowLayout.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/CollectionFlowLayout.swift
index 6e8d0dec7..353b04956 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/CollectionFlowLayout.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/CollectionFlowLayout.swift
@@ -2,7 +2,7 @@
// CollectionFlowLayout.swift
// GiniHealth
//
-// Created by Nadya Karaban on 09.04.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/GiniHealthColors.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/GiniHealthColors.swift
new file mode 100644
index 000000000..fdc1fce90
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/GiniHealthColors.swift
@@ -0,0 +1,43 @@
+//
+// GiniHealthColors.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+import UIKit
+
+extension UIColor {
+ struct GiniHealthColors {
+ static let accent1 = UIColorPreferred(named: "Accent01")
+ static let accent2 = UIColorPreferred(named: "Accent02")
+ static let accent3 = UIColorPreferred(named: "Accent03")
+ static let accent4 = UIColorPreferred(named: "Accent04")
+ static let accent5 = UIColorPreferred(named: "Accent05")
+
+ static let dark1 = UIColorPreferred(named: "Dark01")
+ static let dark2 = UIColorPreferred(named: "Dark02")
+ static let dark3 = UIColorPreferred(named: "Dark03")
+ static let dark4 = UIColorPreferred(named: "Dark04")
+ static let dark5 = UIColorPreferred(named: "Dark05")
+ static let dark6 = UIColorPreferred(named: "Dark06")
+ static let dark7 = UIColorPreferred(named: "Dark07")
+
+ static let light1 = UIColorPreferred(named: "Light01")
+ static let light2 = UIColorPreferred(named: "Light02")
+ static let light3 = UIColorPreferred(named: "Light03")
+ static let light4 = UIColorPreferred(named: "Light04")
+ static let light5 = UIColorPreferred(named: "Light05")
+ static let light6 = UIColorPreferred(named: "Light06")
+ static let light7 = UIColorPreferred(named: "Light07")
+
+ static let feedback1 = UIColorPreferred(named: "Feedback01")
+ static let feedback2 = UIColorPreferred(named: "Feedback02")
+ static let feedback3 = UIColorPreferred(named: "Feedback03")
+ static let feedback4 = UIColorPreferred(named: "Feedback04")
+
+ static let success1 = UIColorPreferred(named: "Success01")
+ static let success2 = UIColorPreferred(named: "Success02")
+ static let success3 = UIColorPreferred(named: "Success03")
+ static let success4 = UIColorPreferred(named: "Success04")
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/NSAttributedString.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/NSAttributedString.swift
new file mode 100644
index 000000000..06bd21441
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/NSAttributedString.swift
@@ -0,0 +1,26 @@
+//
+// NSAttributedString.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+extension NSMutableAttributedString {
+ func addLinkToRange(link: String, range: NSRange, linkFont: UIFont, textToRemove: String?) {
+ var attributes: [NSAttributedString.Key: Any] = [
+ .font: linkFont
+ ]
+ if range.length > 0, let url = URL(string: link) {
+ attributes[.link] = url
+ self.addAttributes(attributes, range: range)
+ if let textToRemove {
+ self.mutableString.replaceOccurrences(of: textToRemove,
+ with: "",
+ options: .caseInsensitive,
+ range: range)
+ }
+ }
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/String.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/String.swift
new file mode 100644
index 000000000..1225d6e48
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/String.swift
@@ -0,0 +1,35 @@
+//
+// String.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+extension String {
+ func toColor() -> UIColor? {
+ return UIColor(hex: String.rgbaHexFrom(rgbHex: self))
+ }
+
+ func canOpenURLString() -> Bool {
+ if let url = URL(string: self) {
+ if UIApplication.shared.canOpenURL(url) {
+ return true
+ }
+ }
+ return false
+ }
+}
+
+public extension String {
+ var numberValue: NSNumber? {
+ let formatter = NumberFormatter()
+ formatter.numberStyle = .decimal
+ return formatter.number(from: self)
+ }
+
+ static func rgbaHexFrom(rgbHex: String) -> String {
+ return "#\(rgbHex)FF"
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/UIColor+Utils.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UIColor+Utils.swift
similarity index 97%
rename from HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/UIColor+Utils.swift
rename to HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UIColor+Utils.swift
index 1a63c2925..1d0f4dec3 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/UIColor+Utils.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UIColor+Utils.swift
@@ -2,7 +2,7 @@
// UIColor+Utils.swift
// GiniHealth
//
-// Created by Nadya Karaban on 07.04.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UIFont.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UIFont.swift
new file mode 100644
index 000000000..0bf3c9819
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UIFont.swift
@@ -0,0 +1,23 @@
+//
+// UIFont.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+extension UIFont.TextStyle {
+ public static let headline1: UIFont.TextStyle = .init(rawValue: "kHeadline1")
+ public static let headline2: UIFont.TextStyle = .init(rawValue: "kHeadline2")
+ public static let headline3: UIFont.TextStyle = .init(rawValue: "kHeadline3")
+ public static let linkBold: UIFont.TextStyle = .init(rawValue: "kLinkBold")
+ public static let subtitle1: UIFont.TextStyle = .init(rawValue: "kSubtitle1")
+ public static let subtitle2: UIFont.TextStyle = .init(rawValue: "kSubtitle2")
+ public static let input: UIFont.TextStyle = .init(rawValue: "kInput")
+ public static let button: UIFont.TextStyle = .init(rawValue: "kButton")
+ public static let body1: UIFont.TextStyle = .init(rawValue: "kBody1")
+ public static let body2: UIFont.TextStyle = .init(rawValue: "kBody2")
+ public static let caption1: UIFont.TextStyle = .init(rawValue: "kCaption1")
+ public static let caption2: UIFont.TextStyle = .init(rawValue: "kCaption2")
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UITapGestureRecognizer.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UITapGestureRecognizer.swift
new file mode 100644
index 000000000..b74760e25
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UITapGestureRecognizer.swift
@@ -0,0 +1,48 @@
+//
+// UITapGestureRecognizer.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+extension UITapGestureRecognizer {
+ func didTapAttributedTextInLabel(label: UILabel, targetText: String) -> Bool {
+ guard let attributedString = label.attributedText, let lblText = label.text else { return false }
+ let targetRange = (lblText as NSString).range(of: targetText)
+ //IMPORTANT label correct font for NSTextStorage needed
+ let mutableAttribString = NSMutableAttributedString(attributedString: attributedString)
+ mutableAttribString.addAttributes(
+ [NSAttributedString.Key.font: label.font ?? UIFont.smallSystemFontSize],
+ range: NSRange(location: 0, length: attributedString.length)
+ )
+ // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
+ let layoutManager = NSLayoutManager()
+ let textContainer = NSTextContainer(size: CGSize.zero)
+ let textStorage = NSTextStorage(attributedString: mutableAttribString)
+
+ // Configure layoutManager and textStorage
+ layoutManager.addTextContainer(textContainer)
+ textStorage.addLayoutManager(layoutManager)
+
+ // Configure textContainer
+ textContainer.lineFragmentPadding = 0.0
+ textContainer.lineBreakMode = label.lineBreakMode
+ textContainer.maximumNumberOfLines = label.numberOfLines
+ let labelSize = label.bounds.size
+ textContainer.size = labelSize
+
+ // Find the tapped character location and compare it to the specified range
+ let locationOfTouchInLabel = self.location(in: label)
+ let textBoundingBox = layoutManager.usedRect(for: textContainer)
+ let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
+ y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
+ let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x,
+ y: locationOfTouchInLabel.y - textContainerOffset.y)
+ let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer,
+ in: textContainer,
+ fractionOfDistanceBetweenInsertionPoints: nil)
+ return NSLocationInRange(indexOfCharacter, targetRange)
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UITextField+Utils.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UITextField+Utils.swift
new file mode 100644
index 000000000..85d9d09d9
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UITextField+Utils.swift
@@ -0,0 +1,32 @@
+//
+// UITextField+Utils.swift
+// GiniHealth
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+import UIKit
+public extension UITextField {
+ var isReallyEmpty: Bool {
+ return text?.trimmingCharacters(in: .whitespaces).isEmpty ?? true
+ }
+
+ func moveSelectedTextRange(from position: UITextPosition, to offset: Int) {
+ if let newSelectedRangeFromTo = self.position(from: position, offset: offset),
+ let newSelectedRange = self.textRange(from: newSelectedRangeFromTo, to: newSelectedRangeFromTo) {
+ self.selectedTextRange = newSelectedRange
+ }
+ }
+}
+
+extension UITextField {
+ func configureWith(configuration: TextFieldConfiguration){
+ self.layer.cornerRadius = configuration.cornerRadius
+ self.layer.borderWidth = configuration.borderWidth
+ self.layer.borderColor = configuration.borderColor.cgColor
+ self.backgroundColor = configuration.backgroundColor
+ self.textColor = configuration.textColor
+ self.attributedPlaceholder = NSAttributedString(string: "",
+ attributes: [NSAttributedString.Key.foregroundColor: configuration.placeholderForegroundColor])
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/UIView+Utils.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UIView+Utils.swift
similarity index 98%
rename from HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/UIView+Utils.swift
rename to HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UIView+Utils.swift
index 5d6d0b4b4..0f312ab14 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/UIView+Utils.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Extensions/UIView+Utils.swift
@@ -2,7 +2,7 @@
// UIView+Utils.swift
// GiniHealth
//
-// Created by Nadya Karaban on 30.03.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniColor.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniColor.swift
index b09cd8dec..63dd80025 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniColor.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniColor.swift
@@ -2,7 +2,7 @@
// GiniColor.swift
// GiniHealth
//
-// Created by Nadya Karaban on 30.03.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
@@ -10,10 +10,11 @@ import UIKit
/**
The `GiniColor` class allows to customize color for the light and the dark modes.
*/
+
@objc public class GiniColor : NSObject {
var lightModeColor: UIColor
var darkModeColor: UIColor
-
+
/**
Creates a GiniColor with the colors for the light and dark modes
@@ -24,4 +25,21 @@ import UIKit
self.lightModeColor = lightModeColor
self.darkModeColor = darkModeColor
}
+
+ func uiColor() -> UIColor {
+ if #available(iOS 13, *) {
+ return UIColor { (UITraitCollection: UITraitCollection) -> UIColor in
+ if UITraitCollection.userInterfaceStyle == .dark {
+ /// Return the color for Dark Mode
+ return self.darkModeColor
+ } else {
+ /// Return the color for Light Mode
+ return self.lightModeColor
+ }
+ }
+ } else {
+ /// Return a fallback color for iOS 12 and lower.
+ return self.lightModeColor
+ }
+ }
}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniCustomButton.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniCustomButton.swift
deleted file mode 100644
index 5b27321b3..000000000
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniCustomButton.swift
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-// GiniCustomButton.swift
-//
-//
-// Created by Nadya Karaban on 10.11.21.
-//
-
-import UIKit
-class GiniCustomButton: UIButton {
- var disabledBackgroundColor: UIColor? = .gray
- var disabledTextColor: UIColor? = .white
- var defaultBackgroundColor: UIColor? {
- didSet {
- backgroundColor = defaultBackgroundColor
- }
- }
- var textColor: UIColor? {
- didSet {
- self.setTitleColor(textColor, for: .normal)
- }
- }
-
- override public var isEnabled: Bool {
- didSet {
- self.backgroundColor = isEnabled ? defaultBackgroundColor : disabledBackgroundColor
- if !self.isEnabled {
- self.setTitleColor(disabledTextColor, for: .disabled)
- }
- }
- }
-
- override var isHighlighted: Bool {
- didSet {
- self.alpha = isHighlighted ? 0.5 : 1
- }
- }
-}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealth.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealth.swift
index 57d99a915..a9f814560 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealth.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealth.swift
@@ -2,7 +2,7 @@
// GiniHealth.swift
// GiniHealth
//
-// Created by Nadya Karaban on 18.02.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
@@ -87,11 +87,10 @@ public struct DataForReview {
In case of failure error that there are no supported banking apps installed.
*/
- private func getInstalledBankingApps(completion: @escaping (Result) -> Void){
- paymentService.paymentProviders { result in
+ private func fetchInstalledBankingApps(completion: @escaping (Result) -> Void) {
+ fetchBankingApps { result in
switch result {
- case let .success(providers):
- self.bankProviders = []
+ case .success(let providers):
for provider in providers {
DispatchQueue.main.async {
if let url = URL(string:provider.appSchemeIOS) {
@@ -110,40 +109,32 @@ public struct DataForReview {
}
case let .failure(error):
DispatchQueue.main.async {
- completion(.failure(.apiError(error)))
-
+ completion(.failure(error))
}
}
}
}
-
+
/**
- Checks if there are any banking app which support Gini Pay Connect functionality installed.
+ Getting a list of the banking apps supported by SDK
- Parameters:
- - completion: An action for processing asynchronous data received from the service with Result type as a paramater. Result is a value that represents either a success or a failure, including an associated value in each case. Completion block called on main thread.
- In success case it includes array of payment providers.
- In case of failure error that there are no supported banking apps installed.
+ - completion: An action for processing asynchronous data received from the service with Result type as a paramater.
+ Result is a value that represents either a success or a failure, including an associated value in each case.
+ In success case it includes array of payment providers supported by SDK.
+ In case of failure error provided by API.
*/
- public func checkIfAnyPaymentProviderAvailable(completion: @escaping (Result) -> Void){
- self.getInstalledBankingApps(completion: completion)
- }
- /**
- Checks if there is any banking app which can support Gini Pay Connect functionality installed.
- - Parameters:
- - appSchemes: A list of [LSApplicationQueriesSchemes] added in Info.plist. Scheme format: ginipay-bank://
- - Returns: a boolean value.
- */
- public func isAnyBankingAppInstalled(appSchemes: [String]) -> Bool {
- for scheme in appSchemes {
- if let url = URL(string:scheme) {
- if UIApplication.shared.canOpenURL(url) {
- return true
- }
+ public func fetchBankingApps(completion: @escaping (Result) -> Void) {
+ paymentService.paymentProviders { result in
+ switch result {
+ case let .success(providers):
+ self.bankProviders = providers
+ completion(.success(self.bankProviders))
+ case let .failure(error):
+ completion(.failure(.apiError(error)))
}
}
- return false
}
/**
@@ -159,8 +150,8 @@ public struct DataForReview {
}
/**
- Checks if the document is payable which looks for iban extraction.
-
+ Checks if the document is payable, looks for iban extraction.
+
- Parameters:
- docId: Id of uploaded document.
- completion: An action for processing asynchronous data received from the service with Result type as a paramater. Result is a value that represents either a success or a failure, including an associated value in each case. Completion block called on main thread.
@@ -287,12 +278,12 @@ public struct DataForReview {
- Parameters:
- requestID: Id of the created payment request.
- - appScheme: App scheme for the selected payment provider
+ - universalLink: Universal link for the selected payment provider
*/
- public func openPaymentProviderApp(requestID: String, appScheme: String) {
+ public func openPaymentProviderApp(requestID: String, universalLink: String) {
let queryItems = [URLQueryItem(name: "id", value: requestID)]
- let urlString = appScheme + "://payment"
+ let urlString = universalLink + "://payment"
var urlComponents = URLComponents(string: urlString)!
urlComponents.queryItems = queryItems
let resultUrl = urlComponents.url!
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealthConfiguration.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealthConfiguration.swift
index 028cd6121..a2cf5e847 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealthConfiguration.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealthConfiguration.swift
@@ -2,7 +2,7 @@
// GiniHealthConfiguration.swift
// GiniHealth
//
-// Created by Nadya Karaban on 30.03.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
@@ -34,127 +34,7 @@ public final class GiniHealthConfiguration: NSObject {
public override init() {}
// MARK: - Payment review screen
-
- /**
- Sets the backgroundColor on the payment review screen
- */
- @objc public var paymentScreenBackgroundColor = GiniColor(lightModeColor: UIColor.black, darkModeColor: UIColor.black)
-
- /**
- Sets the backgroundColor on the payment review screen for input fields container
- */
- @objc public var inputFieldsContainerBackgroundColor = GiniColor(lightModeColor: UIColor.white, darkModeColor: UIColor.white)
-
- /**
- Sets the backgroundColor on the payment review screen for pay button when it's disabled
- */
- @objc public var payButtonDisabledBackgroundColor = GiniColor(lightModeColor: UIColor.from(hex:0xCCCFDB), darkModeColor: UIColor.from(hex:0xCCCFDB))
-
- /**
- Sets the textColor on the payment review screen for pay button when it's disabled
- */
- @objc public var payButtonDisabledTextColor = GiniColor(lightModeColor: .white, darkModeColor: .white)
-
- /**
- Sets the corner radius of the pay button on the payment review screen
- */
- @objc public var payButtonCornerRadius: CGFloat = 6.0
-
- /**
- Sets the font of the pay button title on the payment review screen
- */
- @objc public var payButtonTitleFont: UIFont = UIFont.systemFont(ofSize: 14,
- weight: .bold)
- /**
- Sets the corner radius of the payment input fields on the payment review screen
- */
- @objc public var paymentInputFieldCornerRadius: CGFloat = 6.0
-
- /**
- Sets the border width of the payment input fields on the payment review screen
- */
- @objc public var paymentInputFieldBorderWidth: CGFloat = 0.0
-
- /**
- Sets the border width of the payment input field with selection style on the payment review screen
- */
- @objc public var paymentInputFieldSelectionStyleBorderWidth: CGFloat = 1.0
-
- /**
- Sets the border width of the payment input field with error style on the payment review screen
- */
- @objc public var paymentInputFieldErrorStyleBorderWidth: CGFloat = 1.0
-
- /**
- Sets the error style color and error text color for the payment input fields on the payment review screen
- */
- @objc public var paymentInputFieldErrorStyleColor = GiniColor(lightModeColor: .red, darkModeColor: .red)
-
- /**
- Sets the selection style color for the payment input fields on the payment review screen
- */
- @objc public var paymentInputFieldSelectionStyleColor = GiniColor(lightModeColor: .blue, darkModeColor: .blue)
-
- /**
- Sets the selection background color for the payment input fields on the payment review screen
- */
- @objc public var paymentInputFieldSelectionBackgroundColor = GiniColor(lightModeColor: .white, darkModeColor: .white)
-
- /**
- Sets the background color of the payment input fields on the payment review screen
- */
- @objc public var paymentInputFieldBackgroundColor = GiniColor(lightModeColor: UIColor.from(hex: 0xF2F3F6), darkModeColor: UIColor.from(hex: 0xF2F3F6))
-
- /**
- Sets the text color of the payment input fields on the payment review screen
- */
- @objc public var paymentInputFieldTextColor = GiniColor(lightModeColor: UIColor.from(hex: 0x33406F), darkModeColor: UIColor.from(hex: 0x33406F))
-
- /**
- Sets the placeholder text color of the payment input fields on the payment review screen
- */
- @objc public var paymentInputFieldPlaceholderTextColor = GiniColor(lightModeColor: UIColor.from(hex: 0x999FB7), darkModeColor: UIColor.from(hex: 0x999FB7))
-
- /**
- Sets the text color of the bank selection button on the payment review screen
- */
- @objc public var bankButtonTextColor = GiniColor(lightModeColor: UIColor.from(hex: 0x33406F), darkModeColor: UIColor.from(hex: 0x33406F))
-
- /**
- Sets the background color of the bank selection button on the payment review screen
- */
- @objc public var bankButtonBackgroundColor = GiniColor(lightModeColor: .white, darkModeColor: .white)
-
- /**
- Sets the border width of the the bank button on the payment review screen
- */
- @objc public var bankButtonBorderWidth: CGFloat = 1.0
-
- /**
- Sets the border color of the bank selection button on the payment review screen
- */
- @objc public var bankButtonBorderColor = GiniColor(lightModeColor: UIColor.from(hex: 0xE6E7ED), darkModeColor: UIColor.from(hex: 0xE6E7ED))
-
- /**
- Sets the corner radius of the bank selection button on the payment review screen
- */
- @objc public var bankButtonCornerRadius: CGFloat = 6.0
-
- /**
- Sets the edit icon color of the bank selection button on the payment review screen
- */
- @objc public var bankButtonEditIconColor = GiniColor(lightModeColor: UIColor.from(hex: 0x222222), darkModeColor: UIColor.from(hex: 0x222222))
-
- /**
- Sets the current page indicator on the review screen to the specified color.
- */
- @objc public var currentPageIndicatorTintColor = GiniColor(lightModeColor: UIColor.white, darkModeColor: UIColor.white)
-
- /**
- Sets the page indicator on the review screen to the specified color.
- */
- @objc public var pageIndicatorTintColor = GiniColor(lightModeColor: UIColor.lightGray, darkModeColor: UIColor.lightGray)
-
+
/**
Set to `true` to show a close button on the payment review screen.
*/
@@ -165,87 +45,101 @@ public final class GiniHealthConfiguration: NSObject {
*/
@objc public var paymentReviewStatusBarStyle: UIStatusBarStyle = .default
- // MARK: - Bank selection screen
-
- /**
- Sets the backgroundColor on the bank selection screen.
- */
- @objc public var bankSelectionScreenBackgroundColor = GiniColor(lightModeColor: UIColor.white, darkModeColor: UIColor.white)
-
- /**
- Sets the backgroundColor of the dimmend overlay on the bank selection screen.
- */
- @objc public var bankSelectionDimmedOverlayBackgroundColor = GiniColor(lightModeColor: UIColor.from(hex: 0x00104B66).withAlphaComponent(0.4), darkModeColor: UIColor.from(hex: 0x00104B66).withAlphaComponent(0.4))
-
- /**
- Sets the color of the scroll down view on the bank selection screen.
- */
- @objc public var bankSelectionScrollDownIndicatorViewColor = GiniColor(lightModeColor: UIColor.from(hex: 0xCCCFDB), darkModeColor: UIColor.from(hex: 0xCCCFDB))
-
- /**
- Sets the text color of the title on the bank selection screen.
- */
- @objc public var bankSelectionTitleTextColor = GiniColor(lightModeColor: UIColor.from(hex: 0x00104B), darkModeColor: UIColor.from(hex: 0x00104B))
-
- /**
- Sets the color of the cells separator view on the bank selection screen.
- */
- @objc public var bankSelectionCellSeparatorColor = GiniColor(lightModeColor: UIColor.from(hex: 0xE6E7ED), darkModeColor: UIColor.from(hex: 0xE6E7ED))
-
- /**
- Sets the text color of the cells on the bank selection screen.
- */
- @objc public var bankSelectionCellTextColor = GiniColor(lightModeColor: UIColor.from(hex: 0x00104B), darkModeColor: UIColor.from(hex: 0x00104B))
-
- /**
- Sets the corner radius of the bank icons on the bank selection screen.
- */
- @objc public var bankSelectionCellIconCornerRadius: CGFloat = 0.0
-
- /**
- Sets the text color of the info bar on the payment review screen.
- */
- @objc public var infoBarTextColor = GiniColor(lightModeColor: .white, darkModeColor: .white)
-
- /**
- Sets the background color of the info bar on the payment review screen.
- */
- @objc public var infoBarBackgroundColor = GiniColor(lightModeColor: UIColor.from(hex: 0x7263D0), darkModeColor: UIColor.from(hex: 0x7263D0))
-
- /**
- Sets the corner radius of the info bar on the payment review screen.
- */
- @objc public var infoBarCornerRadius: CGFloat = 12.0
-
+ // MARK: - Button configuration options
+ /**
+ A configuration that defines the appearance of the primary button, including its background color, border color, title color, shadow color, corner radius, border width, shadow radius, and whether to apply a blur effect. It is used for buttons on different UI elements: Payment Component View, Payment Review Screen.
+ */
+ public lazy var primaryButtonConfiguration = ButtonConfiguration(backgroundColor: .GiniHealthColors.accent1.withAlphaComponent(0.4),
+ borderColor: .clear,
+ titleColor: .white,
+ shadowColor: .clear,
+ cornerRadius: 12,
+ borderWidth: 0,
+ shadowRadius: 0,
+ withBlurEffect: false)
+ /**
+ A configuration that defines the appearance of the secondary button, including its background color, border color, title color, shadow color, corner radius, border width, shadow radius, and whether to apply a blur effect. It is used for buttons on different UI elements: Payment Component View.
+ */
+ public lazy var secondaryButtonConfiguration = ButtonConfiguration(backgroundColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark6,
+ darkModeColor: UIColor.GiniHealthColors.light6).uiColor(),
+ borderColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark5,
+ darkModeColor: UIColor.GiniHealthColors.light5).uiColor(),
+ titleColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark1,
+ darkModeColor: UIColor.GiniHealthColors.light1).uiColor(),
+ shadowColor: .clear,
+ cornerRadius: 12,
+ borderWidth: 1,
+ shadowRadius: 0,
+ withBlurEffect: true)
// MARK: - Shared properties
-
- /**
- Sets the font used in the screens by default.
- */
-
- @objc public lazy var customFont = GiniFont(regular: UIFont.systemFont(ofSize: 14,
- weight: .regular),
- bold: UIFont.systemFont(ofSize: 14,
- weight: .bold),
- light: UIFont.systemFont(ofSize: 14,
- weight: .light),
- thin: UIFont.systemFont(ofSize: 14,
- weight: .thin),
- isEnabled: false)
- /**
- Sets the color of the loading indicator to the specified color.
- */
- @objc public var loadingIndicatorColor = GiniColor(lightModeColor: .orange, darkModeColor: .orange)
-
- /**
- Sets the style of the loading indicator.
- */
- @objc public var loadingIndicatorStyle: UIActivityIndicatorView.Style = .whiteLarge
-
+
/**
- Sets the scale of the loading indicator.
- */
- @objc public var loadingIndicatorScale: CGFloat = 1.0
-
+ A default style configuration that defines the appearance of the text field, including its background color, border color, text color, corner radius, border width and the placeholder foreground color. It is used for input text fields on Payment Review Screen.
+ */
+ public lazy var defaultStyleInputFieldConfiguration = TextFieldConfiguration(backgroundColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark6,
+ darkModeColor: UIColor.GiniHealthColors.light6).uiColor(),
+ borderColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark5,
+ darkModeColor: UIColor.GiniHealthColors.light5).uiColor(),
+ textColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark1,
+ darkModeColor: UIColor.GiniHealthColors.light1).uiColor(),
+ cornerRadius: 12.0,
+ borderWidth: 1.0,
+ placeholderForegroundColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark4,
+ darkModeColor: UIColor.GiniHealthColors.light4).uiColor())
+ /**
+ A error style configuration that defines the appearance of the text field, including its background color, border color, text color, corner radius, border width and the placeholder foreground color. It is used for input text fields on Payment Review Screen.
+ */
+ public lazy var errorStyleInputFieldConfiguration = TextFieldConfiguration(backgroundColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark6,
+ darkModeColor: UIColor.GiniHealthColors.light6).uiColor(),
+ borderColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.feedback1,
+ darkModeColor: UIColor.GiniHealthColors.feedback1).uiColor(),
+ textColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark1,
+ darkModeColor: UIColor.GiniHealthColors.light1).uiColor(),
+ cornerRadius: 12.0,
+ borderWidth: 1.0,
+ placeholderForegroundColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark4,
+ darkModeColor: UIColor.GiniHealthColors.light4).uiColor())
+ /**
+ A selection style configuration that defines the appearance of the text field, including its background color, border color, text color, corner radius, border width and the placeholder foreground color. It is used for input text fields on Payment Review Screen.
+ */
+ public lazy var selectionStyleInputFieldConfiguration = TextFieldConfiguration(backgroundColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark6,
+ darkModeColor: UIColor.GiniHealthColors.light6).uiColor(),
+ borderColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.accent1,
+ darkModeColor: UIColor.GiniHealthColors.accent1).uiColor(),
+ textColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark1,
+ darkModeColor: UIColor.GiniHealthColors.light1).uiColor(),
+ cornerRadius: 12.0,
+ borderWidth: 1.0,
+ placeholderForegroundColor: GiniColor(lightModeColor: UIColor.GiniHealthColors.dark4,
+ darkModeColor: UIColor.GiniHealthColors.light4).uiColor())
+
+ // MARK: - Update to custom font
+ /**
+ Allows setting a custom font for specific text styles. The change will affect all screens where a specific text style was used.
+
+ - parameter font: Font that is going to be assosiated with specific text style. You can use scaled font or scale your font with our util method `UIFont.scaledFont(_ font: UIFont, textStyle: UIFont.TextStyle)`
+ - parameter textStyle: Constants that describe the preferred styles for fonts. Please, find additional information [here](https://developer.apple.com/documentation/uikit/uifont/textstyle)
+ */
+ public func updateFont(_ font: UIFont, for textStyle: UIFont.TextStyle) {
+ textStyleFonts[textStyle] = font
+ }
+
+ /**
+ Set dictionary of fonts for available text styles. Used internally.
+ */
+ var textStyleFonts: [UIFont.TextStyle: UIFont] = [
+ .headline1: UIFontMetrics(forTextStyle: .headline1).scaledFont(for: UIFont.systemFont(ofSize: 26, weight: .regular)),
+ .headline2: UIFontMetrics(forTextStyle: .headline2).scaledFont(for: UIFont.systemFont(ofSize: 20, weight: .bold)),
+ .headline3: UIFontMetrics(forTextStyle: .headline3).scaledFont(for: UIFont.systemFont(ofSize: 18, weight: .bold)),
+ .caption1: UIFontMetrics(forTextStyle: .caption1).scaledFont(for: UIFont.systemFont(ofSize: 13, weight: .regular)),
+ .caption2: UIFontMetrics(forTextStyle: .caption2).scaledFont(for: UIFont.systemFont(ofSize: 12, weight: .regular)),
+ .linkBold: UIFontMetrics(forTextStyle: .linkBold).scaledFont(for: UIFont.systemFont(ofSize: 14, weight: .bold)),
+ .subtitle1: UIFontMetrics(forTextStyle: .subtitle1).scaledFont(for: UIFont.systemFont(ofSize: 16, weight: .bold)),
+ .subtitle2: UIFontMetrics(forTextStyle: .subtitle2).scaledFont(for: UIFont.systemFont(ofSize: 14, weight: .medium)),
+ .input: UIFontMetrics(forTextStyle: .input).scaledFont(for: UIFont.systemFont(ofSize: 16, weight: .medium)),
+ .button: UIFontMetrics(forTextStyle: .button).scaledFont(for: UIFont.systemFont(ofSize: 16, weight: .bold)),
+ .body1: UIFontMetrics(forTextStyle: .body1).scaledFont(for: UIFont.systemFont(ofSize: 16, weight: .regular)),
+ .body2: UIFontMetrics(forTextStyle: .body2).scaledFont(for: UIFont.systemFont(ofSize: 14, weight: .regular)),
+ ]
}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealthFont.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealthFont.swift
deleted file mode 100644
index 2080d69b9..000000000
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealthFont.swift
+++ /dev/null
@@ -1,58 +0,0 @@
-//
-// GiniHealthFont.swift
-// GiniHealth
-//
-// Created by Nadya Karaban on 30.03.21.
-//
-
-import UIKit
-/**
- Provides a way to set all possible font weights used in the GiniHealth SDK.
-
- **Possible weights:**
-
- * regular
- * bold
- * light
- * thin
-
- */
-public class GiniFont: NSObject {
- public var regular: UIFont
- public var bold: UIFont
- public var light: UIFont
- public var thin: UIFont
- public private(set) var isEnabled: Bool
-
- public init(regular: UIFont, bold: UIFont, light: UIFont, thin: UIFont, isEnabled: Bool = true) {
- self.regular = regular
- self.bold = bold
- self.light = light
- self.thin = thin
- self.isEnabled = isEnabled
- }
-
- public func with(weight: UIFont.Weight, size: CGFloat, style: UIFont.TextStyle) -> UIFont {
- if #available(iOS 11.0, *) {
- return UIFontMetrics(forTextStyle: style).scaledFont(for: font(for: weight).withSize(size))
- } else {
- return font(for: weight).withSize(size)
- }
- }
-
- private func font(for weight: UIFont.Weight) -> UIFont {
- switch weight {
- case .regular:
- return regular
- case .bold:
- return bold
- case .light:
- return light
- case .thin:
- return thin
- default:
- assertionFailure("\(weight.rawValue) font weight is not supported")
- return regular
- }
- }
-}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealthUtils.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealthUtils.swift
index de15839ae..cd7025042 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealthUtils.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/GiniHealthUtils.swift
@@ -2,7 +2,7 @@
// GiniHealthUtils.swift
// GiniHealth
//
-// Created by Nadya Karaban on 15.04.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
@@ -80,7 +80,7 @@ func decimal(from inputFieldString: String) -> Decimal? {
A help price structure with decimal value and currency code, used in amout inpur field.
*/
-struct Price {
+public struct Price {
// Decimal value
var value: Decimal
// Currency code
@@ -103,7 +103,7 @@ struct Price {
- parameter extractionString: extracted string
*/
- init?(extractionString: String) {
+ public init?(extractionString: String) {
let components = extractionString.components(separatedBy: ":")
@@ -126,11 +126,11 @@ struct Price {
// Currency symbol
var currencySymbol: String? {
return (Locale.current as NSLocale).displayName(forKey: NSLocale.Key.currencySymbol,
- value: currencyCode)
+ value: currencyCode.uppercased())
}
// Formatted string with currency symbol
- var string: String? {
+ public var string: String? {
let result = (Price.stringWithoutSymbol(from: value) ?? "") + " " + (currencySymbol ?? "")
@@ -152,3 +152,60 @@ struct Price {
return trimmedFormattedStringWithoutCurrency
}
}
+
+/**
+ Returns an optional `UIColor` instance with the given `name` preferably from the client's bundle.
+
+ - parameter name: The name of the UIColor from `GiniColors` asset catalog.
+
+ - returns: color if found with name.
+ */
+func UIColorPreferred(named name: String) -> UIColor {
+ if let mainBundleColor = UIColor(named: name,
+ in: Bundle.main,
+ compatibleWith: nil) {
+ return mainBundleColor
+ }
+
+ if let color = UIColor(named: name,
+ in: giniHealthBundleResource(),
+ compatibleWith: nil) {
+ return color
+ } else {
+ fatalError("The color named '\(name)' does not exist.")
+ }
+}
+
+func giniHealthBundleResource() -> Bundle {
+ Bundle.resource
+}
+
+extension Foundation.Bundle {
+ /**
+ The resource bundle associated with the current module.
+ - important: When `GiniHealthSDK` is distributed via Swift Package Manager, it will be synthesized automatically in the name of `Bundle.module`.
+ */
+ static var resource: Bundle = {
+ let moduleName = "GiniHealthSDK"
+ let bundleName = "\(moduleName)_\(moduleName)"
+ let candidates = [
+ // Bundle should be present here when the package is linked into an App.
+ Bundle.main.resourceURL,
+
+ // Bundle should be present here when the package is linked into a framework.
+ Bundle(for: HealthSDKBundleFinder.self).resourceURL,
+
+ // For command-line tools.
+ Bundle.main.bundleURL]
+
+ for candidate in candidates {
+ let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
+ if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
+ return bundle
+ }
+ }
+ return Bundle(for: GiniHealth.self)
+ }()
+}
+
+private class HealthSDKBundleFinder {}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/IBANValidator.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/IBANValidator.swift
index 501afdaf5..75f8d0355 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/IBANValidator.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/IBANValidator.swift
@@ -2,7 +2,7 @@
// IBANValidator.swift
// GiniHealth
//
-// Created by Nadya Karaban on 30.03.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import Foundation
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PageCollectionViewCell.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PageCollectionViewCell.swift
index 2e64d47ba..12ba624fd 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PageCollectionViewCell.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PageCollectionViewCell.swift
@@ -2,7 +2,7 @@
// PageCollectionViewCell.swift
// GiniHealth
//
-// Created by Nadya Karaban on 07.04.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BankSelectionBottomSheet.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BankSelectionBottomSheet.swift
new file mode 100644
index 000000000..c6659e0de
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BankSelectionBottomSheet.swift
@@ -0,0 +1,52 @@
+//
+// BankSelectionBottomSheet.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+final class BankSelectionBottomSheet: UIViewController {
+ var bottomSheet: BanksBottomView! {
+ didSet {
+ setupLayout()
+ }
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ setupViewHierarchy()
+ setupViewAttributes()
+ }
+
+ private func setupViewHierarchy() {
+ view.addSubview(bottomSheet)
+ }
+
+ private func setupViewAttributes() {
+ definesPresentationContext = true
+ view.backgroundColor = bottomSheet.viewModel.dimmingBackgroundColor
+
+ bottomSheet.translatesAutoresizingMaskIntoConstraints = false
+
+ let gesture = UISwipeGestureRecognizer(target: self, action: #selector(dismissViewController))
+ gesture.direction = .down
+ view.isUserInteractionEnabled = true
+ view.addGestureRecognizer(gesture)
+ }
+
+ @objc
+ private func dismissViewController() {
+ bottomSheet.viewModel.didTapOnClose()
+ }
+
+ private func setupLayout() {
+ NSLayoutConstraint.activate([
+ bottomSheet.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
+ bottomSheet.topAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -(bottomSheet.viewModel.bottomViewHeight)),
+ bottomSheet.widthAnchor.constraint(equalToConstant: self.view.frame.width),
+ bottomSheet.heightAnchor.constraint(equalToConstant: bottomSheet.viewModel.bottomViewHeight)
+ ])
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BankSelectionTableViewCell.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BankSelectionTableViewCell.swift
new file mode 100644
index 000000000..8464f3241
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BankSelectionTableViewCell.swift
@@ -0,0 +1,77 @@
+//
+// BankSelectionTableViewCell.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+class BankSelectionTableViewCell: UITableViewCell {
+ static let identifier = "BankSelectionTableViewCell"
+
+ var cellViewModel: BankSelectionTableViewCellModel? {
+ didSet {
+ guard let cellViewModel else { return }
+ cellView.backgroundColor = cellViewModel.backgroundColor
+ bankImageView.image = cellViewModel.bankImageIcon
+ bankImageView.layer.cornerRadius = Constants.bankIconCornerRadius
+ bankImageView.layer.borderWidth = Constants.bankIconBorderWidth
+ bankImageView.layer.borderColor = cellViewModel.bankIconBorderColor.cgColor
+ bankNameLabel.text = cellViewModel.bankName
+ bankNameLabel.font = cellViewModel.bankNameLabelFont
+ bankNameLabel.textColor = cellViewModel.bankNameLabelAccentColor
+
+ setBorder(isSelected: cellViewModel.shouldShowSelectionIcon,
+ selectedBorderColor: cellViewModel.selectedBankBorderColor,
+ notSelectedBorderColor: cellViewModel.notSelectedBankBorderColor)
+
+ appStoreImageView.isHidden = !cellViewModel.shouldShowAppStoreIcon
+ selectionIndicatorImageView.image = cellViewModel.selectionIndicatorImage
+ selectionIndicatorImageView.isHidden = !cellViewModel.shouldShowSelectionIcon
+
+ appStoreBankNameSpacingConstraint.priority = !cellViewModel.shouldShowAppStoreIcon ? .required - 1 : .required
+ selectionIndicatorBankNameSpacingConstraint.priority = !cellViewModel.shouldShowSelectionIcon ? .required - 1 : .required
+ }
+ }
+
+ @IBOutlet private weak var cellView: UIView!
+ @IBOutlet private weak var bankImageView: UIImageView!
+ @IBOutlet private weak var bankNameLabel: UILabel!
+ @IBOutlet private weak var appStoreImageView: UIImageView!
+ @IBOutlet private weak var selectionIndicatorImageView: UIImageView!
+ @IBOutlet private weak var appStoreBankNameSpacingConstraint: NSLayoutConstraint!
+ @IBOutlet private weak var selectionIndicatorBankNameSpacingConstraint: NSLayoutConstraint!
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ selectionStyle = .none
+ }
+
+ private func setBorder(isSelected: Bool, selectedBorderColor: UIColor, notSelectedBorderColor: UIColor) {
+ cellView.roundCorners(corners: .allCorners, radius: Constants.viewCornerRadius)
+ if isSelected {
+ cellView.layer.borderColor = selectedBorderColor.cgColor
+ cellView.layer.borderWidth = Constants.selectedBorderWidth
+ } else {
+ cellView.layer.borderColor = notSelectedBorderColor.cgColor
+ cellView.layer.borderWidth = Constants.notSelectedBorderWidth
+ }
+ }
+
+ override func prepareForReuse() {
+ super.prepareForReuse()
+ appStoreBankNameSpacingConstraint.priority = .required - 1
+ selectionIndicatorBankNameSpacingConstraint.priority = .required - 1
+ }
+}
+
+extension BankSelectionTableViewCell {
+ private enum Constants {
+ static let viewCornerRadius = 8.0
+ static let selectedBorderWidth = 3.0
+ static let notSelectedBorderWidth = 1.0
+ static let bankIconBorderWidth = 1.0
+ static let bankIconCornerRadius = 6.0
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BankSelectionTableViewCellModel.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BankSelectionTableViewCellModel.swift
new file mode 100644
index 000000000..d5286ca54
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BankSelectionTableViewCellModel.swift
@@ -0,0 +1,57 @@
+//
+// BankSelectionTableViewCellModel.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+import GiniHealthAPILibrary
+
+final class BankSelectionTableViewCellModel {
+
+ private var isSelected: Bool = false
+ private var isPaymentProviderInstalled = false
+
+ var shouldShowAppStoreIcon: Bool {
+ !isPaymentProviderInstalled
+ }
+ var shouldShowSelectionIcon: Bool {
+ isPaymentProviderInstalled && isSelected
+ }
+
+ let backgroundColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark7,
+ darkModeColor: UIColor.GiniHealthColors.light7).uiColor()
+
+ private var bankImageIconData: Data?
+ var bankImageIcon: UIImage {
+ if let bankImageIconData {
+ return UIImage(data: bankImageIconData) ?? UIImage()
+ }
+ return UIImage()
+ }
+ var bankIconBorderColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark5,
+ darkModeColor: UIColor.GiniHealthColors.light5).uiColor()
+
+ var bankName: String
+ var bankNameLabelFont: UIFont
+ let bankNameLabelAccentColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark1,
+ darkModeColor: UIColor.GiniHealthColors.light1).uiColor()
+
+ let selectedBankBorderColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.accent1,
+ darkModeColor: UIColor.GiniHealthColors.accent1).uiColor()
+ let notSelectedBankBorderColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark5,
+ darkModeColor: UIColor.GiniHealthColors.light5).uiColor()
+
+ let selectionIndicatorImage = UIImageNamedPreferred(named: "selectionIndicator")
+
+ init(paymentProvider: PaymentProviderAdditionalInfo) {
+ self.isSelected = paymentProvider.isSelected
+ self.isPaymentProviderInstalled = paymentProvider.isInstalled
+ self.bankImageIconData = paymentProvider.paymentProvider.iconData
+ self.bankName = paymentProvider.paymentProvider.name
+
+ let defaultRegularFont: UIFont = UIFont.systemFont(ofSize: 16, weight: .regular)
+ self.bankNameLabelFont = GiniHealthConfiguration.shared.textStyleFonts[.body1] ?? defaultRegularFont
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BanksBottomView.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BanksBottomView.swift
new file mode 100644
index 000000000..2f3ca27df
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BanksBottomView.swift
@@ -0,0 +1,254 @@
+//
+// PaymentProvidersBottomView.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+class BanksBottomView: UIView {
+
+ var viewModel: BanksBottomViewModel! {
+ didSet {
+ setupView()
+ }
+ }
+
+ private lazy var rectangleTopView: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ view.frame = CGRect(x: 0, y: 0, width: Constants.widthTopRectangle, height: Constants.heightTopRectangle)
+ view.roundCorners(corners: .allCorners, radius: Constants.cornerRadiusTopRectangle)
+ view.backgroundColor = viewModel.rectangleColor
+ return view
+ }()
+
+ private lazy var titleView: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ view.frame = CGRect(x: 0, y: 0, width: .greatestFiniteMagnitude, height: Constants.heightTitleView)
+ view.backgroundColor = .clear
+ return view
+ }()
+
+ private lazy var titleLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.text = viewModel.selectBankTitleText
+ label.textColor = viewModel.selectBankLabelAccentColor
+ label.font = viewModel.selectBankLabelFont
+ label.numberOfLines = 1
+ label.lineBreakMode = .byTruncatingTail
+ return label
+ }()
+
+ private lazy var closeTitleIconImageView: UIImageView = {
+ let imageView = UIImageView(image: viewModel.closeTitleIcon.withRenderingMode(.alwaysTemplate))
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ imageView.frame = CGRect(x: 0, y: 0, width: Constants.closeIconSize, height: Constants.closeIconSize)
+ imageView.tintColor = viewModel.closeIconAccentColor
+ imageView.isUserInteractionEnabled = true
+ imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapOnCloseIcon)))
+ return imageView
+ }()
+
+ private lazy var descriptionLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.text = viewModel.descriptionText
+ label.textColor = viewModel.descriptionLabelAccentColor
+ label.font = viewModel.descriptionLabelFont
+ label.numberOfLines = 0
+ return label
+ }()
+
+ private lazy var paymentProvidersTableView: UITableView = {
+ let tableView = UITableView(frame: .zero, style: .plain)
+ tableView.translatesAutoresizingMaskIntoConstraints = false
+ tableView.delegate = self
+ tableView.dataSource = self
+ tableView.register(UINib(nibName: BankSelectionTableViewCell.identifier,
+ bundle: Bundle.resource),
+ forCellReuseIdentifier: BankSelectionTableViewCell.identifier)
+ tableView.estimatedRowHeight = viewModel.rowHeight
+ tableView.rowHeight = viewModel.rowHeight
+ tableView.separatorStyle = .none
+ tableView.tableFooterView = UIView()
+ tableView.backgroundColor = .clear
+ tableView.showsVerticalScrollIndicator = false
+ return tableView
+ }()
+
+ private lazy var poweredByGiniView: PoweredByGiniView = {
+ let view = PoweredByGiniView()
+ view.viewModel = PoweredByGiniViewModel()
+ return view
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupView() {
+ setupViewHierarchy()
+ setupViewAttributes()
+ setupLayout()
+ setupListeners()
+ }
+
+ private func setupViewHierarchy() {
+ self.addSubview(rectangleTopView)
+ self.addSubview(titleView)
+ titleView.addSubview(titleLabel)
+ titleView.addSubview(closeTitleIconImageView)
+ self.addSubview(descriptionLabel)
+ self.addSubview(paymentProvidersTableView)
+ self.addSubview(poweredByGiniView)
+ }
+
+ private func setupViewAttributes() {
+ self.backgroundColor = viewModel.backgroundColor
+ self.roundCorners(corners: [.topLeft, .topRight], radius: Constants.cornerRadiusView)
+
+ let isFullScreen = viewModel.bottomViewHeight >= viewModel.maximumViewHeight
+ paymentProvidersTableView.isScrollEnabled = isFullScreen
+ }
+
+ private func setupLayout() {
+ setupTopRectangleConstraints()
+ setupTitleViewConstraints()
+ setupDescriptionConstraints()
+ setupTableViewConstraints()
+ setupPoweredByGiniConstraints()
+ }
+
+ private func setupListeners() {
+ NotificationCenter.default.addObserver(self,
+ selector: #selector(willEnterForeground),
+ name: UIApplication.willEnterForegroundNotification,
+ object: nil)
+ }
+
+ @objc private func willEnterForeground() {
+ viewModel.updatePaymentProvidersInstalledState()
+ paymentProvidersTableView.reloadData()
+ }
+
+ private func setupTopRectangleConstraints() {
+ NSLayoutConstraint.activate([
+ rectangleTopView.heightAnchor.constraint(equalToConstant: rectangleTopView.frame.height),
+ rectangleTopView.widthAnchor.constraint(equalToConstant: rectangleTopView.frame.width),
+ rectangleTopView.topAnchor.constraint(equalTo: self.topAnchor, constant: Constants.topAnchorTopRectangle),
+ rectangleTopView.centerXAnchor.constraint(equalTo: self.centerXAnchor)
+ ])
+ }
+
+ private func setupTitleViewConstraints() {
+ NSLayoutConstraint.activate([
+ titleView.heightAnchor.constraint(equalToConstant: titleView.frame.height),
+ titleView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constants.viewPaddingConstraint),
+ self.trailingAnchor.constraint(equalTo: titleView.trailingAnchor, constant: Constants.viewPaddingConstraint),
+ titleView.topAnchor.constraint(equalTo: self.topAnchor, constant: Constants.topAnchorTitleView),
+ titleLabel.leadingAnchor.constraint(equalTo: titleView.leadingAnchor),
+ titleLabel.centerYAnchor.constraint(equalTo: titleView.centerYAnchor),
+ closeTitleIconImageView.centerYAnchor.constraint(equalTo: titleView.centerYAnchor),
+ closeTitleIconImageView.heightAnchor.constraint(equalToConstant: closeTitleIconImageView.frame.height),
+ closeTitleIconImageView.widthAnchor.constraint(equalToConstant: closeTitleIconImageView.frame.width),
+ titleView.trailingAnchor.constraint(equalTo: closeTitleIconImageView.trailingAnchor)
+ ])
+ let titleLabelIconViewSpacingConstraint = closeTitleIconImageView.leadingAnchor.constraint(greaterThanOrEqualTo: titleLabel.trailingAnchor, constant: Constants.titleViewTitleIconSpacing)
+ titleLabelIconViewSpacingConstraint.priority = .required - 1
+ titleLabelIconViewSpacingConstraint.isActive = true
+ }
+
+ private func setupDescriptionConstraints() {
+ NSLayoutConstraint.activate([
+ descriptionLabel.topAnchor.constraint(equalTo: titleView.bottomAnchor),
+ descriptionLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constants.viewPaddingConstraint),
+ self.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor, constant: Constants.viewPaddingConstraint)
+ ])
+ }
+
+ private func setupTableViewConstraints() {
+ NSLayoutConstraint.activate([
+ paymentProvidersTableView.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: Constants.viewPaddingConstraint),
+ paymentProvidersTableView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constants.viewPaddingConstraint),
+ self.trailingAnchor.constraint(equalTo: paymentProvidersTableView.trailingAnchor, constant: Constants.viewPaddingConstraint),
+ paymentProvidersTableView.heightAnchor.constraint(equalToConstant: viewModel.heightTableView)
+ ])
+ }
+
+ private func setupPoweredByGiniConstraints() {
+ let poweredByGiniBottomAnchorConstraint = poweredByGiniView.bottomAnchor.constraint(equalTo: poweredByGiniView.bottomAnchor, constant: Constants.viewPaddingConstraint)
+ poweredByGiniBottomAnchorConstraint.priority = .required - 1
+ NSLayoutConstraint.activate([
+ poweredByGiniView.topAnchor.constraint(equalTo: paymentProvidersTableView.bottomAnchor, constant: Constants.viewPaddingConstraint),
+ poweredByGiniView.heightAnchor.constraint(equalToConstant: poweredByGiniView.frame.height),
+ poweredByGiniView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
+ poweredByGiniBottomAnchorConstraint
+ ])
+ }
+
+ @objc
+ private func tapOnCloseIcon() {
+ viewModel.didTapOnClose()
+ }
+
+ private func openPaymentProvidersAppStoreLink(urlString: String?) {
+ guard let urlString = urlString else {
+ print("AppStore link unavailable for this payment provider")
+ return
+ }
+ if let url = URL(string: urlString), UIApplication.shared.canOpenURL(url) {
+ UIApplication.shared.open(url)
+ }
+ }
+}
+
+extension BanksBottomView {
+ enum Constants {
+ static let cornerRadiusView = 12.0
+ static let cornerRadiusTopRectangle = 2.0
+ static let widthTopRectangle = 48
+ static let heightTopRectangle = 4
+ static let topAnchorTopRectangle = 16.0
+ static let heightTitleView = 48.0
+ static let viewPaddingConstraint = 16.0
+ static let topAnchorTitleView = 32.0
+ static let closeIconSize = 24.0
+ static let titleViewTitleIconSpacing = 10.0
+ }
+}
+
+extension BanksBottomView: UITableViewDataSource, UITableViewDelegate {
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ viewModel.paymentProviders.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ guard let cell = tableView.dequeueReusableCell(withIdentifier: BankSelectionTableViewCell.identifier,
+ for: indexPath) as? BankSelectionTableViewCell else {
+ return UITableViewCell()
+ }
+ let invoiceTableViewCellModel = viewModel.paymentProvidersViewModel(paymentProvider: viewModel.paymentProviders[indexPath.row])
+ cell.cellViewModel = invoiceTableViewCellModel
+ return cell
+ }
+
+ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+ viewModel.rowHeight
+ }
+
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ if viewModel.paymentProviders[indexPath.row].isInstalled {
+ viewModel.viewDelegate?.didSelectPaymentProvider(paymentProvider: viewModel.paymentProviders[indexPath.row].paymentProvider)
+ } else {
+ openPaymentProvidersAppStoreLink(urlString: viewModel.paymentProviders[indexPath.row].paymentProvider.appStoreUrlIOS)
+ }
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BanksBottomViewModel.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BanksBottomViewModel.swift
new file mode 100644
index 000000000..722ae76a9
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/BanksBottomViewModel.swift
@@ -0,0 +1,118 @@
+//
+// BanksBottomViewModel.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+import GiniHealthAPILibrary
+
+public protocol PaymentProvidersBottomViewProtocol: AnyObject {
+ func didSelectPaymentProvider(paymentProvider: PaymentProvider)
+ func didTapOnClose()
+}
+
+struct PaymentProviderAdditionalInfo {
+ var isSelected: Bool
+ var isInstalled: Bool
+ let paymentProvider: PaymentProvider
+}
+
+final class BanksBottomViewModel {
+
+ weak var viewDelegate: PaymentProvidersBottomViewProtocol?
+
+ var paymentProviders: [PaymentProviderAdditionalInfo] = []
+ private var selectedPaymentProvider: PaymentProvider?
+
+ let maximumViewHeight: CGFloat = UIScreen.main.bounds.height - Constants.topPaddingView
+ let rowHeight: CGFloat = Constants.cellSizeHeight
+ var bottomViewHeight: CGFloat = 0
+ var heightTableView: CGFloat = 0
+
+ let backgroundColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark7,
+ darkModeColor: UIColor.GiniHealthColors.light7).uiColor()
+ let rectangleColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark5,
+ darkModeColor: UIColor.GiniHealthColors.light5).uiColor()
+ let dimmingBackgroundColor: UIColor = GiniColor(lightModeColor: UIColor.black,
+ darkModeColor: UIColor.white).uiColor().withAlphaComponent(0.4)
+
+ let selectBankTitleText: String = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.selectBank.label",
+ comment: "Select bank text from the top label on payment providers bottom sheet")
+ let selectBankLabelAccentColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark2,
+ darkModeColor: UIColor.GiniHealthColors.light2).uiColor()
+ var selectBankLabelFont: UIFont
+
+ let closeTitleIcon: UIImage = UIImageNamedPreferred(named: "ic_close") ?? UIImage()
+ let closeIconAccentColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark2,
+ darkModeColor: UIColor.GiniHealthColors.light2).uiColor()
+
+ let descriptionText: String = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.paymentproviderslist.description",
+ comment: "Top description text on payment providers bottom sheet")
+ let descriptionLabelAccentColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark3,
+ darkModeColor: UIColor.GiniHealthColors.light3).uiColor()
+ var descriptionLabelFont: UIFont
+
+ init(paymentProviders: PaymentProviders, selectedPaymentProvider: PaymentProvider?) {
+ self.selectedPaymentProvider = selectedPaymentProvider
+
+ let defaultRegularFont: UIFont = UIFont.systemFont(ofSize: 14, weight: .regular)
+ let defaultBoldFont: UIFont = UIFont.systemFont(ofSize: 14, weight: .bold)
+
+ self.selectBankLabelFont = GiniHealthConfiguration.shared.textStyleFonts[.subtitle1] ?? defaultBoldFont
+ self.descriptionLabelFont = GiniHealthConfiguration.shared.textStyleFonts[.caption1] ?? defaultRegularFont
+
+ self.paymentProviders = paymentProviders
+ .filter({ $0.appStoreUrlIOS != nil || isPaymentProviderInstalled(paymentProvider: $0) })
+ .map({ PaymentProviderAdditionalInfo(isSelected: $0.id == selectedPaymentProvider?.id,
+ isInstalled: isPaymentProviderInstalled(paymentProvider: $0),
+ paymentProvider: $0)})
+
+ self.calculateHeights()
+ }
+
+ func updatePaymentProvidersInstalledState() {
+ for index in 0 ..< paymentProviders.count {
+ paymentProviders[index].isInstalled = isPaymentProviderInstalled(paymentProvider: paymentProviders[index].paymentProvider)
+ }
+ if selectedPaymentProvider == nil {
+ selectedPaymentProvider = paymentProviders.first(where: { $0.isInstalled == true })?.paymentProvider
+ if let indexSelected = paymentProviders.firstIndex(where: { $0.paymentProvider.id == selectedPaymentProvider?.id }) {
+ paymentProviders[indexSelected].isSelected = true
+ }
+ }
+ }
+
+ private func calculateHeights() {
+ let totalTableViewHeight = CGFloat(paymentProviders.count) * Constants.cellSizeHeight
+ let totalBottomViewHeight = Constants.blankBottomViewHeight + totalTableViewHeight
+ if totalBottomViewHeight > maximumViewHeight {
+ self.heightTableView = maximumViewHeight - Constants.blankBottomViewHeight
+ self.bottomViewHeight = maximumViewHeight
+ } else {
+ self.heightTableView = totalTableViewHeight
+ self.bottomViewHeight = totalTableViewHeight + Constants.blankBottomViewHeight
+ }
+ }
+
+ func paymentProvidersViewModel(paymentProvider: PaymentProviderAdditionalInfo) -> BankSelectionTableViewCellModel {
+ BankSelectionTableViewCellModel(paymentProvider: paymentProvider)
+ }
+
+ func didTapOnClose() {
+ viewDelegate?.didTapOnClose()
+ }
+
+ private func isPaymentProviderInstalled(paymentProvider: PaymentProvider) -> Bool {
+ paymentProvider.appSchemeIOS.canOpenURLString()
+ }
+}
+
+extension BanksBottomViewModel {
+ enum Constants {
+ static let blankBottomViewHeight: CGFloat = 200.0
+ static let cellSizeHeight: CGFloat = 64.0
+ static let topPaddingView: CGFloat = 100.0
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentComponentView.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentComponentView.swift
new file mode 100644
index 000000000..074cd6eec
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentComponentView.swift
@@ -0,0 +1,268 @@
+//
+// PaymentComponentView.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+final class PaymentComponentView: UIView {
+
+ var viewModel: PaymentComponentViewModel! {
+ didSet {
+ setupView()
+ }
+ }
+
+ private lazy var contentStackView: UIStackView = {
+ let stackView = UIStackView()
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.axis = .vertical
+ stackView.distribution = .fillProportionally
+ stackView.spacing = Constants.contentStackViewSpacing
+ return stackView
+ }()
+
+ private lazy var moreInformationStackView: UIStackView = {
+ let stackView = UIStackView()
+ stackView.translatesAutoresizingMaskIntoConstraints = false
+ stackView.axis = .horizontal
+ stackView.distribution = .fillProportionally
+ return stackView
+ }()
+
+ // We need our label into a view for layout purposes. Stackviews require views in order to satisfy all dynamic constraints
+ private lazy var moreInformationLabelView: UIView = {
+ return UIView()
+ }()
+
+ private lazy var moreInformationLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.textColor = viewModel.moreInformationLabelTextColor
+ label.font = viewModel.moreInformationLabelFont
+ label.numberOfLines = 0
+ label.text = viewModel.moreInformationLabelText
+
+ let moreInformationActionableAttributtedString = NSMutableAttributedString(string: viewModel.moreInformationLabelText)
+ let moreInformationPartString = (viewModel.moreInformationLabelText as NSString).range(of: viewModel.moreInformationActionablePartText)
+ moreInformationActionableAttributtedString.addAttribute(.foregroundColor,
+ value: viewModel.moreInformationLabelTextColor,
+ range: moreInformationPartString)
+ moreInformationActionableAttributtedString.addAttribute(NSAttributedString.Key.underlineStyle,
+ value: NSUnderlineStyle.single.rawValue,
+ range: moreInformationPartString)
+ moreInformationActionableAttributtedString.addAttribute(NSAttributedString.Key.font,
+ value: viewModel.moreInformationLabelLinkFont,
+ range: moreInformationPartString)
+ label.attributedText = moreInformationActionableAttributtedString
+
+ let tapOnMoreInformation = UITapGestureRecognizer(target: self,
+ action: #selector(tapOnMoreInformationLabelAction(gesture:)))
+ label.isUserInteractionEnabled = true
+ label.addGestureRecognizer(tapOnMoreInformation)
+
+ label.attributedText = moreInformationActionableAttributtedString
+ return label
+ }()
+
+ private lazy var moreInformationButton: UIButton = {
+ let button = UIButton(type: .system)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ let image = UIImageNamedPreferred(named: viewModel.moreInformationIconName)
+ button.setImage(image, for: .normal)
+ button.tintColor = viewModel.moreInformationAccentColor
+ button.addTarget(self, action: #selector(tapOnMoreInformationButtonAction), for: .touchUpInside)
+ return button
+ }()
+
+ private lazy var selectBankView: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ view.frame = CGRect(x: 0, y: 0, width: .greatestFiniteMagnitude, height: Constants.bankViewHeight)
+ return view
+ }()
+
+ private lazy var selectYourBankLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.text = viewModel.selectYourBankLabelText
+ label.textColor = viewModel.selectYourBankAccentColor
+ label.font = viewModel.selectYourBankLabelFont
+ label.numberOfLines = 0
+ return label
+ }()
+
+ private lazy var selectBankButton: PaymentSecondaryButton = {
+ let button = PaymentSecondaryButton()
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.frame = CGRect(x: 0, y: 0, width: .greatestFiniteMagnitude, height: Constants.buttonViewHeight)
+ button.configure(with: viewModel.giniHealthConfiguration.secondaryButtonConfiguration)
+ button.customConfigure(labelText: viewModel.bankNameLabelText,
+ leftImageIcon: viewModel.bankImageIcon,
+ rightImageIcon: viewModel.chevronDownIconName,
+ rightImageTintColor: viewModel.chevronDownIconColor,
+ isPaymentProviderInstalled: viewModel.isPaymentProviderInstalled,
+ notInstalledTextColor: viewModel.notInstalledBankTextColor)
+ return button
+ }()
+
+ private lazy var payInvoiceButton: PaymentPrimaryButton = {
+ let button = PaymentPrimaryButton()
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.frame = CGRect(x: 0, y: 0, width: .greatestFiniteMagnitude, height: Constants.buttonViewHeight)
+ button.configure(with: viewModel.giniHealthConfiguration.primaryButtonConfiguration)
+ button.customConfigure(paymentProviderColors: viewModel.paymentProviderColors,
+ isPaymentProviderInstalled: viewModel.isPaymentProviderInstalled,
+ text: viewModel.payInvoiceLabelText)
+ return button
+ }()
+
+ private lazy var poweredByGiniView: PoweredByGiniView = {
+ let view = PoweredByGiniView()
+ view.viewModel = PoweredByGiniViewModel()
+ return view
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupView() {
+ self.translatesAutoresizingMaskIntoConstraints = false
+ self.frame = CGRect(x: 0, y: 0, width: .greatestFiniteMagnitude, height: Constants.viewHeight)
+
+ self.backgroundColor = viewModel.backgroundColor
+
+ self.addSubview(contentStackView)
+
+ contentStackView.addArrangedSubview(moreInformationStackView)
+ contentStackView.addArrangedSubview(selectBankView)
+ moreInformationLabelView.addSubview(moreInformationLabel)
+ moreInformationStackView.addArrangedSubview(moreInformationLabelView)
+ moreInformationStackView.addArrangedSubview(moreInformationButton)
+ selectBankView.addSubview(selectYourBankLabel)
+ selectBankView.addSubview(selectBankButton)
+ selectBankView.addSubview(payInvoiceButton)
+ selectBankView.addSubview(poweredByGiniView)
+
+ activateAllConstraints()
+ setupGestures()
+ }
+
+ private func activateAllConstraints() {
+ activateContentStackViewConstraints()
+ activateSelectBankButtonConstraints()
+ activatePayInvoiceButtonConstraints()
+ activatePoweredByGiniViewConstraints()
+ activateMoreInformationViewConstraints()
+ }
+
+ private func setupGestures() {
+ payInvoiceButton.didTapButton = { [weak self] in
+ self?.tapOnPayInvoiceView()
+ }
+ selectBankButton.didTapButton = { [weak self] in
+ self?.tapOnBankPicker()
+ }
+ }
+
+ private func activateContentStackViewConstraints() {
+ // Content StackView Constraints
+ let contentViewHeightConstraint = heightAnchor.constraint(equalToConstant: frame.height)
+ contentViewHeightConstraint.priority = .required - 1 // We need this to silent warnings
+
+ let contentViewBottomAnchorConstraint = contentStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 4)
+ contentViewBottomAnchorConstraint.priority = .required - 1
+
+ NSLayoutConstraint.activate([
+ contentViewHeightConstraint,
+ contentStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ contentStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ contentStackView.topAnchor.constraint(equalTo: topAnchor, constant: Constants.contentTopPadding),
+ contentStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: Constants.contentBottomPadding),
+ contentViewBottomAnchorConstraint
+ ])
+ }
+
+ private func activateSelectBankButtonConstraints() {
+ let selectBankViewHeightConstraint = selectBankView.heightAnchor.constraint(equalToConstant: selectBankView.frame.height)
+ selectBankViewHeightConstraint.priority = .required - 1
+ NSLayoutConstraint.activate([
+ selectBankViewHeightConstraint,
+ selectYourBankLabel.leadingAnchor.constraint(equalTo: selectBankView.leadingAnchor),
+ selectYourBankLabel.topAnchor.constraint(equalTo: selectBankView.topAnchor),
+ selectYourBankLabel.trailingAnchor.constraint(equalTo: selectBankView.trailingAnchor),
+ selectBankButton.heightAnchor.constraint(equalToConstant: selectBankButton.frame.height),
+ selectBankButton.leadingAnchor.constraint(equalTo: selectBankView.leadingAnchor),
+ selectBankButton.trailingAnchor.constraint(equalTo: selectBankView.trailingAnchor),
+ selectBankButton.topAnchor.constraint(equalTo: selectYourBankLabel.bottomAnchor, constant: Constants.contentBottomPadding)
+ ])
+ }
+
+ private func activatePayInvoiceButtonConstraints() {
+ NSLayoutConstraint.activate([
+ payInvoiceButton.heightAnchor.constraint(equalToConstant: payInvoiceButton.frame.height),
+ payInvoiceButton.leadingAnchor.constraint(equalTo: selectBankView.leadingAnchor),
+ payInvoiceButton.trailingAnchor.constraint(equalTo: selectBankView.trailingAnchor),
+ payInvoiceButton.topAnchor.constraint(equalTo: selectBankButton.bottomAnchor, constant: Constants.invoicePickerBankPadding)
+ ])
+ }
+
+ private func activatePoweredByGiniViewConstraints() {
+ NSLayoutConstraint.activate([
+ poweredByGiniView.heightAnchor.constraint(equalToConstant: poweredByGiniView.frame.height),
+ poweredByGiniView.trailingAnchor.constraint(equalTo: selectBankView.trailingAnchor),
+ poweredByGiniView.topAnchor.constraint(equalTo: payInvoiceButton.bottomAnchor, constant: Constants.contentBottomPadding)
+ ])
+ }
+
+ private func activateMoreInformationViewConstraints() {
+ NSLayoutConstraint.activate([
+ moreInformationLabel.leadingAnchor.constraint(equalTo: moreInformationLabelView.leadingAnchor),
+ moreInformationLabel.trailingAnchor.constraint(equalTo: moreInformationLabelView.trailingAnchor),
+ moreInformationLabel.centerYAnchor.constraint(equalTo: moreInformationLabelView.centerYAnchor)
+ ])
+ }
+
+ @objc
+ private func tapOnMoreInformationLabelAction(gesture: UITapGestureRecognizer) {
+ if gesture.didTapAttributedTextInLabel(label: moreInformationLabel,
+ targetText: viewModel.moreInformationActionablePartText) {
+ viewModel.tapOnMoreInformation()
+ }
+ }
+
+ @objc
+ private func tapOnMoreInformationButtonAction(gesture: UITapGestureRecognizer) {
+ viewModel.tapOnMoreInformation()
+ }
+
+ @objc
+ private func tapOnBankPicker() {
+ viewModel.tapOnBankPicker()
+ }
+
+ @objc
+ private func tapOnPayInvoiceView() {
+ viewModel.tapOnPayInvoiceView()
+ }
+}
+
+extension PaymentComponentView {
+ private enum Constants {
+ static let viewHeight: CGFloat = 240
+ static let bankViewHeight: CGFloat = 185
+ static let buttonViewHeight: CGFloat = 56
+ static let contentStackViewSpacing: CGFloat = 12
+ static let bankIconSize: CGFloat = 32
+ static let contentTopPadding: CGFloat = 16
+ static let contentBottomPadding: CGFloat = 4
+ static let invoicePickerBankPadding: CGFloat = 8
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentComponentViewModel.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentComponentViewModel.swift
new file mode 100644
index 000000000..20450d2df
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentComponentViewModel.swift
@@ -0,0 +1,151 @@
+//
+// PaymentComponentViewModel.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+import GiniHealthAPILibrary
+
+/**
+ Delegate to inform about the actions happened of the custom payment component view.
+ You may find out when the user tapped on more information area, on the payment provider picker or on the pay invoice button
+
+ */
+public protocol PaymentComponentViewProtocol: AnyObject {
+ /**
+ Called when the user tapped on the more information actionable label or the information icon
+
+ - parameter documentId: Id of document
+ */
+ func didTapOnMoreInformation(documentId: String?)
+
+ /**
+ Called when the user tapped on payment provider picker to change the selected payment provider or install it
+
+ - parameter documentId: Id of document
+ */
+ func didTapOnBankPicker(documentId: String?)
+
+ /**
+ Called when the user tapped on the pay the invoice button to pay the invoice/document
+ - parameter documentId: Id of document
+ */
+ func didTapOnPayInvoice(documentId: String?)
+}
+
+/**
+ Helping extension for using the PaymentComponentViewProtocol methods without the document ID. This should be kept by the document view model and passed hierarchically from there.
+
+ */
+extension PaymentComponentViewProtocol {
+ public func didTapOnMoreInformation() {
+ didTapOnMoreInformation(documentId: nil)
+ }
+ public func didTapOnBankPicker() {
+ didTapOnBankPicker(documentId: nil)
+ }
+ public func didTapOnPayInvoice() {
+ didTapOnPayInvoice(documentId: nil)
+ }
+}
+
+final class PaymentComponentViewModel {
+ var giniHealthConfiguration = GiniHealthConfiguration.shared
+
+ let backgroundColor: UIColor = UIColor.from(giniColor: GiniColor(lightModeColor: .clear,
+ darkModeColor: .clear))
+
+ // More information part
+ let moreInformationAccentColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark2,
+ darkModeColor: UIColor.GiniHealthColors.light2).uiColor()
+ let moreInformationLabelTextColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark4,
+ darkModeColor: UIColor.GiniHealthColors.light4).uiColor()
+ let moreInformationLabelText = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.moreInformation.label",
+ comment: "Text for more information label")
+ let moreInformationActionablePartText = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.moreInformation.underlined.part",
+ comment: "Text for more information actionable part from the label")
+ var moreInformationLabelFont: UIFont
+ var moreInformationLabelLinkFont: UIFont
+ let moreInformationIconName = "info.circle"
+
+ // Select bank label
+ let selectYourBankLabelText = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.selectYourBank.label",
+ comment: "Text for the select your bank label that's above the payment provider picker")
+ let selectYourBankLabelFont: UIFont
+ let selectYourBankAccentColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark1,
+ darkModeColor: UIColor.GiniHealthColors.light1).uiColor()
+
+ // Bank image icon
+ private var bankImageIconData: Data?
+ var bankImageIcon: UIImage {
+ if let bankImageIconData {
+ return UIImage(data: bankImageIconData) ?? UIImage()
+ }
+ return UIImage()
+ }
+
+ // Bank name label
+ private var bankName: String?
+ var bankNameLabelText: String {
+ if let bankName, !bankName.isEmpty {
+ return isPaymentProviderInstalled ? bankName : placeholderBankNameText
+ }
+ return placeholderBankNameText
+ }
+ let notInstalledBankTextColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark4,
+ darkModeColor: UIColor.GiniHealthColors.light4).uiColor()
+ private let placeholderBankNameText: String = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.selectBank.label",
+ comment: "Placeholder text used when there isn't a payment provider app installed")
+
+ let chevronDownIconName: String = "iconChevronDown"
+ let chevronDownIconColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.light7,
+ darkModeColor: UIColor.GiniHealthColors.light1).uiColor()
+
+ // Payment provider colors
+ var paymentProviderColors: ProviderColors?
+
+ // Pay invoice label
+ let payInvoiceLabelText: String = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.payInvoice.label",
+ comment: "Title label used for the pay invoice button")
+
+ // Payment provider installation status
+ var isPaymentProviderInstalled: Bool {
+ if let paymentProviderScheme, let url = URL(string: paymentProviderScheme), UIApplication.shared.canOpenURL(url) {
+ return true
+ }
+ return false
+ }
+ private var paymentProviderScheme: String?
+
+ weak var delegate: PaymentComponentViewProtocol?
+
+ var documentId: String?
+
+ init(paymentProvider: PaymentProvider?) {
+ let defaultRegularFont: UIFont = UIFont.systemFont(ofSize: 13, weight: .regular)
+ let defaultBoldFont: UIFont = UIFont.systemFont(ofSize: 14, weight: .bold)
+ let defaultMediumFont: UIFont = UIFont.systemFont(ofSize: 14, weight: .medium)
+ self.moreInformationLabelFont = giniHealthConfiguration.textStyleFonts[.caption1] ?? defaultRegularFont
+ self.moreInformationLabelLinkFont = giniHealthConfiguration.textStyleFonts[.linkBold] ?? defaultBoldFont
+ self.selectYourBankLabelFont = giniHealthConfiguration.textStyleFonts[.subtitle2] ?? defaultMediumFont
+
+ self.bankImageIconData = paymentProvider?.iconData
+ self.bankName = paymentProvider?.name
+ self.paymentProviderColors = paymentProvider?.colors
+ self.paymentProviderScheme = paymentProvider?.appSchemeIOS
+ }
+
+ func tapOnMoreInformation() {
+ delegate?.didTapOnMoreInformation(documentId: documentId)
+ }
+
+ func tapOnBankPicker() {
+ delegate?.didTapOnBankPicker(documentId: documentId)
+ }
+
+ func tapOnPayInvoiceView() {
+ delegate?.didTapOnPayInvoice(documentId: documentId)
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentComponentsController.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentComponentsController.swift
new file mode 100644
index 000000000..8867641aa
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentComponentsController.swift
@@ -0,0 +1,247 @@
+//
+// PaymentComponentController.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+import GiniHealthAPILibrary
+/**
+ Protocol used to provide updates on the current status of the Payment Components Controller.
+ Uses a callback mechanism to handle payment provider requests.
+ */
+public protocol PaymentComponentsControllerProtocol: AnyObject {
+ func isLoadingStateChanged(isLoading: Bool) // Because we can't use Combine
+ func didFetchedPaymentProviders()
+}
+
+/**
+ The `PaymentComponentsController` class allows control over the payment components.
+ */
+public final class PaymentComponentsController: NSObject {
+ /// handling the Payment Component Controller delegate
+ public weak var delegate: PaymentComponentsControllerProtocol?
+ /// handling the Payment Component view delegate
+ public weak var viewDelegate: PaymentComponentViewProtocol?
+ /// handling the Payment Bottom view delegate
+ public weak var bottomViewDelegate: PaymentProvidersBottomViewProtocol?
+
+ private var giniHealth: GiniHealth
+ private var paymentProviders: PaymentProviders = []
+ private var installedPaymentProviders: PaymentProviders = []
+
+ /// storing the current selected payment provider
+ public var selectedPaymentProvider: PaymentProvider?
+
+ /// reponsible for storing the loading state of the controller and passing it to the delegate listeners
+ var isLoading: Bool = false {
+ didSet {
+ delegate?.isLoadingStateChanged(isLoading: isLoading)
+ }
+ }
+
+ var paymentComponentView: PaymentComponentView!
+
+ /**
+ Initializer of the Payment Component Controller class.
+
+ - Parameters:
+ - giniHealth: An instance of GiniHealth initialized with GiniHealthAPI.
+ - Returns:
+ - instance of the payment component controller class
+ */
+ public init(giniHealth: GiniHealth) {
+ self.giniHealth = giniHealth
+ super.init()
+ setupListeners()
+ }
+
+ deinit {
+ NotificationCenter.default.removeObserver(self)
+ }
+
+ private func setupListeners() {
+ NotificationCenter.default.addObserver(self,
+ selector: #selector(willEnterForeground),
+ name: UIApplication.willEnterForegroundNotification,
+ object: nil)
+ }
+
+ /**
+ Retrieves the default installed payment provider, if available.
+ - Returns: a Payment Provider object.
+ */
+ private func defaultInstalledPaymentProvider() -> PaymentProvider? {
+ savedPaymentProvider() ?? installedPaymentProviders.first
+ }
+
+ /**
+ Loads the payment providers list and stores them.
+ - note: Also triggers a function that checks if the payment providers are installed.
+ */
+ public func loadPaymentProviders() {
+ self.isLoading = true
+ self.giniHealth.fetchBankingApps { [weak self] result in
+ self?.isLoading = false
+ switch result {
+ case let .success(paymentProviders):
+ self?.paymentProviders = paymentProviders
+ self?.checkInstalledPaymentProviders()
+ self?.selectedPaymentProvider = self?.defaultInstalledPaymentProvider()
+ self?.delegate?.didFetchedPaymentProviders()
+ case let .failure(error):
+ print("Couldn't load payment providers: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ @objc
+ private func willEnterForeground() {
+ DispatchQueue.main.async {
+ if !self.checkPaymentProviderIsInstalled(paymentProvider: self.selectedPaymentProvider) {
+ self.loadPaymentProviders()
+ }
+ }
+ }
+
+ private func checkInstalledPaymentProviders() {
+ installedPaymentProviders = []
+ for paymentProvider in paymentProviders {
+ if checkPaymentProviderIsInstalled(paymentProvider: paymentProvider) {
+ self.installedPaymentProviders.append(paymentProvider)
+ }
+ }
+ }
+
+ private func storeDefaultPaymentProvider(paymentProvider: PaymentProvider) {
+ do {
+ let encoder = JSONEncoder()
+ let data = try encoder.encode(paymentProvider)
+ UserDefaults.standard.set(data, forKey: Constants.kDefaultPaymentProvider)
+ } catch {
+ print("Unable to encode payment provider: (\(error))")
+ }
+ }
+
+ private func savedPaymentProvider() -> PaymentProvider? {
+ if let data = UserDefaults.standard.data(forKey: Constants.kDefaultPaymentProvider) {
+ do {
+ let decoder = JSONDecoder()
+ let paymentProvider = try decoder.decode(PaymentProvider.self, from: data)
+ if self.installedPaymentProviders.contains(where: { $0.id == paymentProvider.id }) {
+ return paymentProvider
+ }
+ } catch {
+ print("Unable to decode payment provider: (\(error))")
+ }
+ }
+ return nil
+ }
+
+ private func checkPaymentProviderIsInstalled(paymentProvider: PaymentProvider?) -> Bool {
+ if let appSchemeIOS = paymentProvider?.appSchemeIOS, let url = URL(string: appSchemeIOS) {
+ return UIApplication.shared.canOpenURL(url)
+ }
+ return false
+ }
+
+ /**
+ Checks if the document is payable by extracting the IBAN.
+ - Parameters:
+ - docId: The ID of the uploaded document.
+ - completion: A closure for processing asynchronous data received from the service. It has a Result type parameter, representing either success or failure. The completion block is called on the main thread.
+ In the case of success, it includes a boolean value indicating whether the IBAN was extracted successfully.
+ In case of failure, it returns an error from the server side.
+ */
+ public func checkIfDocumentIsPayable(docId: String, completion: @escaping (Result) -> Void) {
+ giniHealth.checkIfDocumentIsPayable(docId: docId, completion: completion)
+ }
+
+ /**
+ Provides a custom Gini view that contains more information, bank selection if available and a tappable button to pay the document/invoice
+
+ - Parameters:
+ - Returns: a custom view
+ */
+ public func paymentView(documentId: String) -> UIView {
+ paymentComponentView = PaymentComponentView()
+ let paymentComponentViewModel = PaymentComponentViewModel(paymentProvider: selectedPaymentProvider)
+ paymentComponentViewModel.delegate = viewDelegate
+ paymentComponentViewModel.documentId = documentId
+ paymentComponentView.viewModel = paymentComponentViewModel
+ return paymentComponentView
+ }
+
+ public func bankSelectionBottomSheet() -> UIViewController {
+ let paymentProvidersBottomView = BanksBottomView()
+ let paymentProvidersBottomViewModel = BanksBottomViewModel(paymentProviders: paymentProviders,
+ selectedPaymentProvider: selectedPaymentProvider)
+ paymentProvidersBottomViewModel.viewDelegate = self
+ paymentProvidersBottomView.viewModel = paymentProvidersBottomViewModel
+ let bankSelectionBottomSheet = BankSelectionBottomSheet()
+ bankSelectionBottomSheet.bottomSheet = paymentProvidersBottomView
+ return bankSelectionBottomSheet
+ }
+
+ public func loadPaymentReviewScreenFor(documentID: String, trackingDelegate: GiniHealthTrackingDelegate?, completion: @escaping (UIViewController?, GiniHealthError?) -> Void) {
+ self.isLoading = true
+ self.giniHealth.fetchDataForReview(documentId: documentID) { [weak self] result in
+ self?.isLoading = false
+ switch result {
+ case .success(let data):
+ guard let self else {
+ completion(nil, nil)
+ return
+ }
+ let vc = PaymentReviewViewController.instantiate(with: self.giniHealth,
+ data: data,
+ selectedPaymentProvider: self.selectedPaymentProvider,
+ trackingDelegate: trackingDelegate)
+ completion(vc, nil)
+ case .failure(let error):
+ completion(nil, error)
+ }
+ }
+ }
+
+ public func paymentInfoViewController() -> UIViewController {
+ let paymentInfoViewController = PaymentInfoViewController()
+ let paymentInfoViewModel = PaymentInfoViewModel(paymentProviders: paymentProviders)
+ paymentInfoViewController.viewModel = paymentInfoViewModel
+ return paymentInfoViewController
+ }
+}
+
+extension PaymentComponentsController: PaymentComponentViewProtocol {
+ public func didTapOnMoreInformation(documentId: String?) {
+ viewDelegate?.didTapOnMoreInformation()
+ }
+
+ public func didTapOnBankPicker(documentId: String?) {
+ viewDelegate?.didTapOnBankPicker()
+ }
+
+ public func didTapOnPayInvoice(documentId: String?) {
+ viewDelegate?.didTapOnPayInvoice()
+ }
+}
+
+extension PaymentComponentsController: PaymentProvidersBottomViewProtocol {
+ public func didSelectPaymentProvider(paymentProvider: PaymentProvider) {
+ selectedPaymentProvider = paymentProvider
+ storeDefaultPaymentProvider(paymentProvider: paymentProvider)
+ bottomViewDelegate?.didSelectPaymentProvider(paymentProvider: paymentProvider)
+ }
+
+ public func didTapOnClose() {
+ bottomViewDelegate?.didTapOnClose()
+ }
+}
+
+extension PaymentComponentsController {
+ private enum Constants {
+ static let kDefaultPaymentProvider = "defaultPaymentProvider"
+ }
+}
+
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoAnswerTableViewCell.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoAnswerTableViewCell.swift
new file mode 100644
index 000000000..dd030a2fc
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoAnswerTableViewCell.swift
@@ -0,0 +1,78 @@
+//
+// PaymentInfoAnswerTableViewCell.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+final class PaymentInfoAnswerTableViewCell: UITableViewCell {
+ static let identifier = "PaymentInfoAnswerTableViewCell"
+
+ private lazy var textView: UITextView = {
+ let textView = UITextView()
+ textView.translatesAutoresizingMaskIntoConstraints = false
+ textView.isScrollEnabled = false
+ textView.isEditable = false
+ textView.textContainerInset = .zero
+ textView.textContainer.lineFragmentPadding = 0
+ textView.isUserInteractionEnabled = true
+ textView.backgroundColor = .clear
+ return textView
+ }()
+
+ var cellViewModel: PaymentInfoAnswerTableViewModel? {
+ didSet {
+ configure()
+ }
+ }
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ self.backgroundColor = .clear
+ contentView.addSubview(textView)
+ setupConstraints()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupConstraints() {
+ NSLayoutConstraint.activate([
+ textView.topAnchor.constraint(equalTo: contentView.topAnchor),
+ textView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
+ textView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
+ textView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -Constants.bottomPadding)
+ ])
+ }
+
+ private func configure() {
+ guard let cellViewModel = cellViewModel else { return }
+ textView.attributedText = cellViewModel.answerAttributedText
+ textView.textColor = cellViewModel.answerTextColor
+ textView.linkTextAttributes = cellViewModel.answerLinkAttributes
+ textView.layoutIfNeeded()
+ }
+}
+
+struct PaymentInfoAnswerTableViewModel {
+ let answerAttributedText: NSAttributedString
+ let answerTextColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark1,
+ darkModeColor: UIColor.GiniHealthColors.light1).uiColor()
+ let answerLinkColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.accent1,
+ darkModeColor: UIColor.GiniHealthColors.accent1).uiColor()
+ let answerLinkAttributes: [NSAttributedString.Key: Any]
+
+ init(answerAttributedText: NSAttributedString) {
+ self.answerAttributedText = answerAttributedText
+ self.answerLinkAttributes = [.foregroundColor: answerLinkColor]
+ }
+}
+
+extension PaymentInfoAnswerTableViewCell {
+ private enum Constants {
+ static let bottomPadding = 16.0
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoBankCollectionViewCell.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoBankCollectionViewCell.swift
new file mode 100644
index 000000000..76051538f
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoBankCollectionViewCell.swift
@@ -0,0 +1,75 @@
+//
+// PaymentInfoBankCollectionViewCell.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+final class PaymentInfoBankCollectionViewCell: UICollectionViewCell {
+
+ static let identifier = "PaymentInfoBankCollectionViewCell"
+
+ var cellViewModel: PaymentInfoBankCollectionViewCellModel? {
+ didSet {
+ guard let cellViewModel else { return }
+ bankIconImageView.image = cellViewModel.bankImageIcon
+ bankIconImageView.layer.borderColor = cellViewModel.borderColor.cgColor
+ }
+ }
+
+ private lazy var bankIconImageView: UIImageView = {
+ let imageView = UIImageView()
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ imageView.contentMode = .scaleAspectFit
+ imageView.clipsToBounds = true
+ imageView.backgroundColor = .clear
+ imageView.roundCorners(corners: .allCorners, radius: Constants.bankIconCornerRadius)
+ imageView.layer.borderWidth = Constants.bankIconBorderWidth
+ return imageView
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ contentView.addSubview(bankIconImageView)
+ setupConstraints()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(frame:) has not been implemented")
+ }
+
+ private func setupConstraints() {
+ NSLayoutConstraint.activate([
+ bankIconImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
+ bankIconImageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
+ bankIconImageView.topAnchor.constraint(equalTo: contentView.topAnchor),
+ bankIconImageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
+ ])
+ }
+}
+
+final class PaymentInfoBankCollectionViewCellModel {
+ private var bankImageIconData: Data?
+ var bankImageIcon: UIImage {
+ if let bankImageIconData {
+ return UIImage(data: bankImageIconData) ?? UIImage()
+ }
+ return UIImage()
+ }
+
+ var borderColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark5,
+ darkModeColor: UIColor.GiniHealthColors.light5).uiColor()
+
+ init(bankImageIconData: Data?) {
+ self.bankImageIconData = bankImageIconData
+ }
+}
+
+extension PaymentInfoBankCollectionViewCell {
+ private enum Constants {
+ static let bankIconCornerRadius = 6.0
+ static let bankIconBorderWidth = 1.0
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoQuestionHeaderViewCell.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoQuestionHeaderViewCell.swift
new file mode 100644
index 000000000..ffaf2f582
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoQuestionHeaderViewCell.swift
@@ -0,0 +1,101 @@
+//
+// PaymentInfoQuestionHeaderViewCell.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+final class PaymentInfoQuestionHeaderViewCell: UIView {
+ var didTapSelectButton: (() -> Void) = {}
+
+ var headerViewModel: PaymentInfoQuestionHeaderViewModel? {
+ didSet {
+ guard let headerViewModel else { return }
+ configureView(viewModel: headerViewModel)
+ }
+ }
+
+ private lazy var titleLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.lineBreakMode = .byWordWrapping
+ label.numberOfLines = 0
+ label.textAlignment = .left
+ return label
+ }()
+
+ private lazy var extendedImageView: UIImageView = {
+ let imageView = UIImageView()
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ imageView.contentMode = .scaleAspectFit
+ imageView.clipsToBounds = true
+ imageView.backgroundColor = .clear
+ imageView.frame = CGRect(x: 0, y: 0, width: Constants.imageSize, height: Constants.imageSize)
+ return imageView
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ addSubview(titleLabel)
+ addSubview(extendedImageView)
+ setupConstraints()
+ addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tappedOnView)))
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ private func configureView(viewModel: PaymentInfoQuestionHeaderViewModel) {
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.lineHeightMultiple = Constants.titleLineHeight
+ titleLabel.attributedText = NSMutableAttributedString(string: viewModel.titleText,
+ attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle])
+ titleLabel.textColor = viewModel.titleTextColor
+ titleLabel.font = viewModel.titleFont
+ extendedImageView.image = UIImageNamedPreferred(named: viewModel.extendedIconName)
+ }
+
+ private func setupConstraints() {
+ NSLayoutConstraint.activate([
+ titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
+ titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
+ titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Constants.titleRightPadding),
+ extendedImageView.widthAnchor.constraint(equalToConstant: extendedImageView.frame.width),
+ extendedImageView.heightAnchor.constraint(equalToConstant: extendedImageView.frame.height),
+ extendedImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ extendedImageView.centerYAnchor.constraint(equalTo: centerYAnchor)
+ ])
+ }
+
+ @objc private func tappedOnView() {
+ didTapSelectButton()
+ }
+}
+
+final class PaymentInfoQuestionHeaderViewModel {
+ var titleText: String
+ var titleFont: UIFont
+ let titleTextColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark1,
+ darkModeColor: UIColor.GiniHealthColors.light1).uiColor()
+ var extendedIconName: String
+ private let plusIcon = "ic_plus"
+ private let minusIcon = "ic_minus"
+
+ init(title: String, isExtended: Bool) {
+ self.titleText = title
+ let giniConfiguration = GiniHealthConfiguration.shared
+ self.titleFont = giniConfiguration.textStyleFonts[.body1] ?? UIFont.systemFont(ofSize: 16)
+ self.extendedIconName = isExtended ? minusIcon : plusIcon
+ }
+}
+
+extension PaymentInfoQuestionHeaderViewCell {
+ private enum Constants {
+ static let titleLineHeight = 1.15
+ static let titleRightPadding = 85.0
+ static let imageSize = 24.0
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoViewController.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoViewController.swift
new file mode 100644
index 000000000..ed34bf600
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoViewController.swift
@@ -0,0 +1,358 @@
+//
+// PaymentInfoViewController.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+class PaymentInfoViewController: UIViewController {
+
+ var viewModel: PaymentInfoViewModel! {
+ didSet {
+ setupView()
+ }
+ }
+
+ private lazy var scrollView: UIScrollView = {
+ let scrollView = UIScrollView()
+ scrollView.showsVerticalScrollIndicator = false
+ scrollView.showsHorizontalScrollIndicator = false
+ scrollView.isScrollEnabled = true
+ scrollView.translatesAutoresizingMaskIntoConstraints = false
+ return scrollView
+ }()
+
+ private lazy var contentView: UIView = {
+ let contentView = UIView()
+ contentView.translatesAutoresizingMaskIntoConstraints = false
+ return contentView
+ }()
+
+ private lazy var bankIconsCollectionView: UICollectionView = {
+ let collectionLayout = UICollectionViewFlowLayout()
+ collectionLayout.scrollDirection = .horizontal
+ collectionLayout.minimumInteritemSpacing = Constants.bankIconsSpacing
+
+ let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionLayout)
+ collectionView.translatesAutoresizingMaskIntoConstraints = false
+ collectionView.frame = CGRect(x: 0, y: 0, width: .greatestFiniteMagnitude, height: Constants.bankIconsWidth)
+ collectionView.dataSource = self
+ collectionView.delegate = self
+ collectionView.backgroundColor = .clear
+ collectionView.allowsSelection = false
+ collectionView.showsHorizontalScrollIndicator = false
+ collectionView.isScrollEnabled = true
+ collectionView.register(PaymentInfoBankCollectionViewCell.self,
+ forCellWithReuseIdentifier: PaymentInfoBankCollectionViewCell.identifier)
+ return collectionView
+ }()
+
+ private lazy var poweredByGiniView: PoweredByGiniView = {
+ let view = PoweredByGiniView()
+ view.viewModel = PoweredByGiniViewModel()
+ return view
+ }()
+
+ private lazy var payBillsTitleLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.font = viewModel.payBillsTitleFont
+ label.textColor = viewModel.payBillsTitleTextColor
+ label.lineBreakMode = .byWordWrapping
+ label.numberOfLines = 0
+ label.textAlignment = .left
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.lineHeightMultiple = Constants.payBillsTitleLineHeight
+ label.attributedText = NSMutableAttributedString(string: viewModel.payBillsTitleText,
+ attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle])
+ return label
+ }()
+
+ private lazy var payBillsDescriptionTextView: UITextView = {
+ let textView = UITextView()
+ textView.translatesAutoresizingMaskIntoConstraints = false
+ textView.isScrollEnabled = false
+ textView.isEditable = false
+ textView.textContainerInset = .zero
+ textView.textContainer.lineFragmentPadding = 0
+ textView.isUserInteractionEnabled = true
+ textView.backgroundColor = .clear
+ textView.attributedText = viewModel.payBillsDescriptionAttributedText
+ textView.linkTextAttributes = viewModel.payBillsDescriptionLinkAttributes
+ return textView
+ }()
+
+ private lazy var questionsTitleLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.font = viewModel.questionsTitleFont
+ label.textColor = viewModel.questionsTitleTextColor
+ label.lineBreakMode = .byWordWrapping
+ label.numberOfLines = 0
+ label.textAlignment = .left
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.lineHeightMultiple = Constants.questionsTitleLineHeight
+ label.attributedText = NSMutableAttributedString(string: viewModel.questionsTitleText,
+ attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle])
+ return label
+ }()
+
+ private lazy var questionsTableView: UITableView = {
+ let tableView = UITableView()
+ tableView.delegate = self
+ tableView.dataSource = self
+ tableView.register(PaymentInfoAnswerTableViewCell.self,
+ forCellReuseIdentifier: PaymentInfoAnswerTableViewCell.identifier)
+ tableView.separatorStyle = .singleLine
+ tableView.backgroundColor = .clear
+ tableView.showsVerticalScrollIndicator = false
+ tableView.translatesAutoresizingMaskIntoConstraints = false
+ tableView.isScrollEnabled = false
+ tableView.rowHeight = UITableView.automaticDimension
+ tableView.estimatedRowHeight = Constants.questionTitleHeight
+ tableView.estimatedSectionHeaderHeight = Constants.questionTitleHeight
+ tableView.estimatedSectionFooterHeight = 1.0
+ if #available(iOS 15.0, *) {
+ tableView.sectionHeaderTopPadding = 0
+ }
+ return tableView
+ }()
+
+ private var heightsQuestionsTableView: [NSLayoutConstraint] = []
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.title = viewModel.titleText
+ }
+
+ private func setupView() {
+ setupViewHierarchy()
+ setupViewAttributes()
+ setupViewConstraints()
+ }
+
+ private func setupViewHierarchy() {
+ view.addSubview(scrollView)
+ scrollView.addSubview(contentView)
+ contentView.addSubview(bankIconsCollectionView)
+ contentView.addSubview(poweredByGiniView)
+ contentView.addSubview(payBillsTitleLabel)
+ contentView.addSubview(payBillsDescriptionTextView)
+ contentView.addSubview(questionsTitleLabel)
+ contentView.addSubview(questionsTableView)
+ }
+
+ private func setupViewAttributes() {
+ view.backgroundColor = viewModel.backgroundColor
+ }
+
+ private func setupViewConstraints() {
+ setupContentViewConstraints()
+ setupBankIconsCollectionViewConstraints()
+ setupPoweredByGiniConstraints()
+ setupPayBillsConstraints()
+ setupQuestionsConstraints()
+ }
+
+ private func setupContentViewConstraints() {
+ NSLayoutConstraint.activate([
+ scrollView.topAnchor.constraint(equalTo: view.topAnchor),
+ scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
+
+ contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
+ contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
+ contentView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor),
+ contentView.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor),
+ ])
+ }
+
+ private func setupBankIconsCollectionViewConstraints() {
+ NSLayoutConstraint.activate([
+ bankIconsCollectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
+ bankIconsCollectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
+ bankIconsCollectionView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: Constants.bankIconsTopSpacing),
+ bankIconsCollectionView.heightAnchor.constraint(equalToConstant: bankIconsCollectionView.frame.height)
+ ])
+ }
+
+ private func setupPoweredByGiniConstraints() {
+ NSLayoutConstraint.activate([
+ poweredByGiniView.topAnchor.constraint(equalTo: bankIconsCollectionView.bottomAnchor, constant: Constants.poweredByGiniTopPadding),
+ poweredByGiniView.heightAnchor.constraint(equalToConstant: poweredByGiniView.frame.height),
+ poweredByGiniView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
+ ])
+ }
+
+ private func setupPayBillsConstraints() {
+ NSLayoutConstraint.activate([
+ payBillsTitleLabel.topAnchor.constraint(equalTo: poweredByGiniView.bottomAnchor, constant: Constants.payBillsTitleTopPadding),
+ payBillsTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Constants.leftRightPadding),
+ payBillsTitleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -Constants.leftRightPadding),
+ payBillsTitleLabel.heightAnchor.constraint(lessThanOrEqualToConstant: Constants.maxPayBillsTitleHeight),
+ payBillsDescriptionTextView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Constants.leftRightPadding),
+ payBillsDescriptionTextView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -Constants.payBillsDescriptionRightPadding),
+ payBillsDescriptionTextView.topAnchor.constraint(equalTo: payBillsTitleLabel.bottomAnchor, constant: Constants.payBillsDescriptionTopPadding),
+ payBillsDescriptionTextView.heightAnchor.constraint(greaterThanOrEqualToConstant: Constants.minPayBillsDescriptionHeight),
+ ])
+ }
+
+ private func setupQuestionsConstraints() {
+ NSLayoutConstraint.activate([
+ questionsTitleLabel.topAnchor.constraint(equalTo: payBillsDescriptionTextView.bottomAnchor, constant: Constants.questionsTitleTopPadding),
+ questionsTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Constants.leftRightPadding),
+ questionsTitleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -Constants.leftRightPadding),
+ questionsTableView.topAnchor.constraint(equalTo: questionsTitleLabel.bottomAnchor),
+ questionsTableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Constants.leftRightPadding),
+ questionsTableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -Constants.leftRightPadding),
+ questionsTableView.heightAnchor.constraint(greaterThanOrEqualToConstant: Double(viewModel.questions.count) * Constants.questionTitleHeight),
+ questionsTableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -Constants.leftRightPadding)
+ ])
+ }
+
+ private func extended(section: Int) {
+ let isExtended = viewModel.questions[section].isExtended
+ viewModel.questions[section].isExtended = !isExtended
+ questionsTableView.reloadData()
+ questionsTableView.layoutIfNeeded()
+ // Small hack needed to satisfy automatic dimension table view inside scrollView
+ NSLayoutConstraint.deactivate(heightsQuestionsTableView)
+ heightsQuestionsTableView = [questionsTableView.heightAnchor.constraint(greaterThanOrEqualToConstant: questionsTableView.contentSize.height)]
+ NSLayoutConstraint.activate(heightsQuestionsTableView)
+ }
+}
+
+extension PaymentInfoViewController: UICollectionViewDataSource {
+ func collectionView(_ collectionView: UICollectionView,
+ cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PaymentInfoBankCollectionViewCell.identifier,
+ for: indexPath) as? PaymentInfoBankCollectionViewCell else {
+ return UICollectionViewCell()
+ }
+ cell.cellViewModel = PaymentInfoBankCollectionViewCellModel(bankImageIconData: viewModel.paymentProviders[indexPath.row].iconData)
+ return cell
+ }
+
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+ return viewModel.paymentProviders.count
+ }
+
+ func numberOfSections(in collectionView: UICollectionView) -> Int {
+ return 1
+ }
+}
+
+extension PaymentInfoViewController: UICollectionViewDelegateFlowLayout {
+ func collectionView(_ collectionView: UICollectionView,
+ layout collectionViewLayout: UICollectionViewLayout,
+ sizeForItemAt indexPath: IndexPath) -> CGSize {
+ return CGSize(width: Constants.bankIconsWidth, height: Constants.bankIconsHeight)
+ }
+
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
+ let cellCount = Double(viewModel.paymentProviders.count)
+ if cellCount > 0 {
+ let cellWidth = Constants.bankIconsWidth
+
+ let totalCellWidth = cellWidth * cellCount + Constants.bankIconsSpacing * (cellCount - 1)
+ let contentWidth = collectionView.frame.size.width - (2 * Constants.leftRightPadding)
+
+ if totalCellWidth < contentWidth {
+ let padding = (contentWidth - totalCellWidth) / 2.0
+ return UIEdgeInsets(top: 0, left: padding, bottom: 0, right: padding)
+ } else {
+ return UIEdgeInsets(top: 0, left: Constants.leftRightPadding, bottom: 0, right: Constants.leftRightPadding)
+ }
+ }
+ return UIEdgeInsets.zero
+ }
+}
+
+extension PaymentInfoViewController: UITableViewDelegate, UITableViewDataSource {
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ if viewModel.questions[section].isExtended {
+ return 1
+ }
+ return 0
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ guard let cell = tableView.dequeueReusableCell(withIdentifier: PaymentInfoAnswerTableViewCell.identifier,
+ for: indexPath) as? PaymentInfoAnswerTableViewCell else {
+ return UITableViewCell()
+ }
+ let answerTableViewCellModel = PaymentInfoAnswerTableViewModel(answerAttributedText: viewModel.questions[indexPath.section].description)
+ cell.cellViewModel = answerTableViewCellModel
+ return cell
+ }
+
+ func numberOfSections(in tableView: UITableView) -> Int {
+ viewModel.questions.count
+ }
+
+ func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+ let viewHeader = PaymentInfoQuestionHeaderViewCell(frame: CGRect(x: 0, y: 0, width: .greatestFiniteMagnitude, height: Constants.questionTitleHeight))
+ viewHeader.headerViewModel = PaymentInfoQuestionHeaderViewModel(title: viewModel.questions[section].title, isExtended: viewModel.questions[section].isExtended)
+ viewHeader.didTapSelectButton = { [weak self] in
+ self?.extended(section: section)
+ }
+ return viewHeader
+ }
+
+ func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+ Constants.questionTitleHeight
+ }
+
+ func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
+ guard section < viewModel.questions.count - 1 else { return UIView() }
+ let separatorView = UIView(frame: CGRect(x: 0, y: 0, width: .greatestFiniteMagnitude, height: Constants.questionSectionSeparatorHeight))
+ separatorView.backgroundColor = viewModel.separatorColor
+ return separatorView
+ }
+
+ func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
+ Constants.questionSectionSeparatorHeight
+ }
+
+ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+ UITableView.automaticDimension
+ }
+
+ func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
+ Constants.estimatedAnswerHeight
+ }
+}
+
+extension PaymentInfoViewController {
+ private enum Constants {
+ static let paragraphSpacing = 10.0
+
+ static let leftRightPadding = 16.0
+
+ static let bankIconsSpacing = 5.0
+ static let bankIconsTopSpacing = 15.0
+ static let bankIconsWidth = 36.0
+ static let bankIconsHeight = 36.0
+
+ static let poweredByGiniTopPadding = 7.0
+
+ static let payBillsTitleTopPadding = 16.0
+ static let payBillsTitleLineHeight = 1.26
+ static let maxPayBillsTitleHeight = 100.0
+ static let payBillsDescriptionTopPadding = 8.0
+ static let payBillsDescriptionRightPadding = 31.0
+ static let minPayBillsDescriptionHeight = 100.0
+
+ static let questionsTitleTopPadding = 24.0
+ static let questionsTitleLineHeight = 1.28
+
+ static let questionTitleHeight = 72.0
+ static let questionSectionSeparatorHeight = 1.0
+
+ static let estimatedAnswerHeight = 250.0
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoViewModel.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoViewModel.swift
new file mode 100644
index 000000000..7cfa3baf6
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentInfoViewModel.swift
@@ -0,0 +1,150 @@
+//
+// PaymentInfoViewModel.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+import GiniHealthAPILibrary
+
+struct FAQSection {
+ let title: String
+ var description: NSAttributedString
+ var isExtended: Bool
+}
+
+final class PaymentInfoViewModel {
+
+ var paymentProviders: PaymentProviders
+
+ let backgroundColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark7,
+ darkModeColor: UIColor.GiniHealthColors.light7).uiColor()
+
+ let titleText: String = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.paymentinfo.title.label",
+ comment: "Payment Info title label text")
+
+ let payBillsTitleText: String = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.paymentinfo.payBills.title.label",
+ comment: "Payment Info pay bills title label text")
+ let payBillsTitleFont: UIFont
+ let payBillsTitleTextColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark1,
+ darkModeColor: UIColor.GiniHealthColors.light1).uiColor()
+
+ private let payBillsDescriptionText: String = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.paymentinfo.payBills.description.label",
+ comment: "Payment Info pay bills description text")
+ var payBillsDescriptionAttributedText: NSMutableAttributedString = NSMutableAttributedString()
+ var payBillsDescriptionLinkAttributes: [NSAttributedString.Key: Any]
+ private let payBillsDescriptionFont: UIFont
+ private let payBillsDescriptionTextColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark1,
+ darkModeColor: UIColor.GiniHealthColors.light1).uiColor()
+ private let giniWebsiteText = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.paymentinfo.payBills.description.clickable.text",
+ comment: "Word range that's clickable in pay bills description")
+ private let giniFont: UIFont
+ private let giniURLText = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.paymentinfo.gini.link",
+ comment: "Gini website link url")
+
+ let questionsTitleText: String = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.paymentinfo.questions.title.label",
+ comment: "Payment Info questions title label text")
+ let questionsTitleFont: UIFont
+ let questionsTitleTextColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark1,
+ darkModeColor: UIColor.GiniHealthColors.light1).uiColor()
+
+ private var answersFont: UIFont
+ private let answerPrivacyPolicyText = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.paymentinfo.questions.answer.clickable.text",
+ comment: "Payment info answers clickable privacy policy")
+ private let privacyPolicyURLText = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.paymentinfo.gini.privacypolicy.link",
+ comment: "Gini privacy policy link url")
+ private var linksFont: UIFont
+ private let linksTextColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.accent1,
+ darkModeColor: UIColor.GiniHealthColors.accent1).uiColor()
+
+ let separatorColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark5,
+ darkModeColor: UIColor.GiniHealthColors.light5).uiColor()
+
+ var questions: [FAQSection] = []
+
+ init(paymentProviders: PaymentProviders) {
+ self.paymentProviders = paymentProviders
+ .filter({ $0.appStoreUrlIOS != nil || $0.appStoreUrlIOS?.canOpenURLString() ?? false })
+
+ let giniHealthConfiguration = GiniHealthConfiguration.shared
+
+ let defaultRegularFont: UIFont = UIFont.systemFont(ofSize: 13, weight: .regular)
+ let defaultBoldFont: UIFont = UIFont.systemFont(ofSize: 13, weight: .bold)
+
+ payBillsTitleFont = giniHealthConfiguration.textStyleFonts[.subtitle1] ?? defaultBoldFont
+ payBillsDescriptionFont = giniHealthConfiguration.textStyleFonts[.body2] ?? defaultRegularFont
+ questionsTitleFont = giniHealthConfiguration.textStyleFonts[.subtitle1] ?? defaultBoldFont
+ giniFont = giniHealthConfiguration.textStyleFonts[.button] ?? defaultBoldFont
+ answersFont = giniHealthConfiguration.textStyleFonts[.body2] ?? defaultRegularFont
+ linksFont = giniHealthConfiguration.textStyleFonts[.linkBold] ?? defaultBoldFont
+
+ payBillsDescriptionLinkAttributes = [.foregroundColor: linksTextColor]
+
+ configurePayBillsGiniLink()
+ setupQuestions()
+ }
+
+ private func setupQuestions() {
+ for index in 1 ... Constants.numberOfQuestions {
+ let answerAttributedString = answerWithAttributes(answer: NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.paymentinfo.questions.answer.\(index)",
+ comment: "Answers description"))
+ let questionSection = FAQSection(title: NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.paymentinfo.questions.question.\(index)",
+ comment: "Questions titles"),
+ description: textWithLinks(linkFont: linksFont,
+ attributedString: answerAttributedString),
+ isExtended: false)
+ questions.append(questionSection)
+ }
+ }
+
+ private func configurePayBillsGiniLink() {
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.lineHeightMultiple = Constants.payBillsDescriptionLineHeight
+ paragraphStyle.paragraphSpacing = Constants.payBillsParagraphSpacing
+ payBillsDescriptionAttributedText = NSMutableAttributedString(string: payBillsDescriptionText,
+ attributes: [.paragraphStyle: paragraphStyle,
+ .font: payBillsDescriptionFont,
+ .foregroundColor: payBillsTitleTextColor])
+ payBillsDescriptionAttributedText = textWithLinks(linkFont: giniFont,
+ attributedString: payBillsDescriptionAttributedText)
+ }
+
+ private func answerWithAttributes(answer: String) -> NSMutableAttributedString {
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.lineHeightMultiple = Constants.answersLineHeight
+ paragraphStyle.paragraphSpacing = Constants.answersParagraphSpacing
+ let answerAttributedText = NSMutableAttributedString(string: answer,
+ attributes: [.font: answersFont, .paragraphStyle: paragraphStyle])
+ return answerAttributedText
+ }
+
+ private func textWithLinks(linkFont: UIFont, attributedString: NSMutableAttributedString) -> NSMutableAttributedString {
+ let attributedString = attributedString
+ let giniRange = (attributedString.string as NSString).range(of: giniWebsiteText)
+ attributedString.addLinkToRange(link: giniURLText,
+ range: giniRange,
+ linkFont: linkFont,
+ textToRemove: Constants.linkTextToRemove)
+ let privacyPolicyRange = (attributedString.string as NSString).range(of: answerPrivacyPolicyText)
+ attributedString.addLinkToRange(link: privacyPolicyURLText,
+ range: privacyPolicyRange,
+ linkFont: linkFont,
+ textToRemove: Constants.linkTextToRemove)
+ return attributedString
+ }
+}
+
+extension PaymentInfoViewModel {
+ private enum Constants {
+ static let numberOfQuestions = 6
+
+ static let payBillsDescriptionLineHeight = 1.32
+ static let payBillsParagraphSpacing = 10.0
+
+ static let answersLineHeight = 1.32
+ static let answersParagraphSpacing = 10.0
+
+ static let linkTextToRemove = "[LINK]"
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentPrimaryButton.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentPrimaryButton.swift
new file mode 100644
index 000000000..a6263d7fc
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentPrimaryButton.swift
@@ -0,0 +1,115 @@
+//
+// PaymentPrimaryButton.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+import GiniHealthAPILibrary
+
+final class PaymentPrimaryButton: UIView {
+
+ private var giniHealthConfiguration = GiniHealthConfiguration.shared
+
+ var didTapButton: (() -> Void)?
+
+ private lazy var contentView: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapOnPayInvoiceView)))
+ return view
+ }()
+
+ private lazy var titleLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.numberOfLines = 1
+ label.adjustsFontSizeToFitWidth = true
+ label.textAlignment = .center
+ return label
+ }()
+
+ private lazy var leftImageView: UIImageView = {
+ let imageView = UIImageView()
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ imageView.frame = CGRect(x: 0, y: 0, width: Constants.bankIconSize, height: Constants.bankIconSize)
+ return imageView
+ }()
+
+ init() {
+ super.init(frame: .zero)
+ addSubview(contentView)
+ contentView.addSubview(titleLabel)
+ setupConstraints()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupConstraints() {
+ NSLayoutConstraint.activate([
+ contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ contentView.topAnchor.constraint(equalTo: topAnchor),
+ contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
+ contentView.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor),
+ contentView.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
+ contentView.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor)
+ ])
+ }
+
+ private func setupLeftImageConstraints() {
+ leftImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Constants.contentLeadingPadding).isActive = true
+ leftImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
+ leftImageView.widthAnchor.constraint(equalToConstant: leftImageView.frame.width).isActive = true
+ leftImageView.heightAnchor.constraint(equalToConstant: leftImageView.frame.height).isActive = true
+ }
+
+ @objc private func tapOnPayInvoiceView() {
+ didTapButton?()
+ }
+}
+
+extension PaymentPrimaryButton {
+ func configure(with configuration: ButtonConfiguration) {
+ self.contentView.backgroundColor = configuration.backgroundColor
+ self.contentView.layer.cornerRadius = configuration.cornerRadius
+ self.contentView.layer.borderColor = configuration.borderColor.cgColor
+ self.contentView.layer.shadowColor = configuration.shadowColor.cgColor
+
+ self.titleLabel.textColor = configuration.titleColor
+
+ if let buttonFont = giniHealthConfiguration.textStyleFonts[.button] {
+ self.titleLabel.font = buttonFont
+ }
+ }
+
+ func customConfigure(paymentProviderColors: ProviderColors?, isPaymentProviderInstalled: Bool, text: String, leftImageData: Data? = nil) {
+ if let backgroundHexColor = paymentProviderColors?.background.toColor(), isPaymentProviderInstalled {
+ contentView.backgroundColor = backgroundHexColor
+ }
+ contentView.isUserInteractionEnabled = isPaymentProviderInstalled
+
+ titleLabel.text = text
+ if let textHexColor = paymentProviderColors?.text.toColor() {
+ titleLabel.textColor = textHexColor
+ }
+ // Left image appears only on Payment Review Screen
+ if let leftImageData {
+ contentView.addSubview(leftImageView)
+ setupLeftImageConstraints()
+ leftImageView.roundCorners(corners: .allCorners, radius: Constants.bankIconCornerRadius)
+ leftImageView.image = UIImage(data: leftImageData)
+ }
+ }
+}
+
+extension PaymentPrimaryButton {
+ private enum Constants {
+ static let bankIconSize: CGFloat = 36
+ static let bankIconCornerRadius: CGFloat = 8
+ static let contentLeadingPadding: CGFloat = 19
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentSecondaryButton.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentSecondaryButton.swift
new file mode 100644
index 000000000..0d0d7171a
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PaymentSecondaryButton.swift
@@ -0,0 +1,144 @@
+//
+// PaymentSecondaryButton.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+final class PaymentSecondaryButton: UIView {
+
+ private var giniHealthConfiguration = GiniHealthConfiguration.shared
+
+ var didTapButton: (() -> Void)?
+
+ private lazy var contentView: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapOnBankPicker)))
+ return view
+ }()
+
+ private lazy var leftImageView: UIImageView = {
+ let imageView = UIImageView()
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ imageView.frame = CGRect(x: 0, y: 0, width: Constants.bankIconSize, height: Constants.bankIconSize)
+ return imageView
+ }()
+
+ private lazy var titleLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.numberOfLines = 1
+ label.lineBreakMode = .byTruncatingTail
+ return label
+ }()
+
+ private lazy var rightImageView: UIImageView = {
+ let imageView = UIImageView()
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ imageView.frame = CGRect(x: 0, y: 0, width: Constants.chevronIconSize, height: Constants.chevronIconSize)
+ return imageView
+ }()
+
+ init() {
+ super.init(frame: .zero)
+ addSubview(contentView)
+ contentView.addSubview(leftImageView)
+ contentView.addSubview(titleLabel)
+ contentView.addSubview(rightImageView)
+ setupConstraints()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupConstraints() {
+ NSLayoutConstraint.activate([
+ contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ contentView.topAnchor.constraint(equalTo: topAnchor),
+ contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
+ rightImageView.widthAnchor.constraint(equalToConstant: rightImageView.frame.width),
+ rightImageView.heightAnchor.constraint(equalToConstant: rightImageView.frame.height),
+ contentView.trailingAnchor.constraint(equalTo: rightImageView.trailingAnchor, constant: Constants.contentTrailingPadding),
+ rightImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+ rightImageView.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: Constants.bankNameChevronIconPadding)
+ ])
+ }
+
+ private func activateBankImageViewConstraints(isPaymentProviderInstalled: Bool) {
+ if isPaymentProviderInstalled {
+ leftImageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Constants.contentLeadingPadding).isActive = true
+ leftImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
+ leftImageView.widthAnchor.constraint(equalToConstant: leftImageView.frame.width).isActive = true
+ leftImageView.heightAnchor.constraint(equalToConstant: leftImageView.frame.height).isActive = true
+ let bankNameBankViewConstraint = titleLabel.leadingAnchor.constraint(equalTo: leftImageView.trailingAnchor, constant: Constants.contentLeadingPadding)
+ bankNameBankViewConstraint.priority = .required - 1 // fix needed because of embeded views in cells issue. We need this to silent the "Unable to simultaneously satisfy constraints" warning
+ bankNameBankViewConstraint.isActive = true
+ leftImageView.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor).isActive = true
+ } else {
+ let bankNameLeadingSuperviewConstraint = titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Constants.contentLeadingPadding)
+ bankNameLeadingSuperviewConstraint.priority = .required - 1
+ bankNameLeadingSuperviewConstraint.isActive = true
+ contentView.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor).isActive = true
+ }
+ }
+
+ @objc
+ private func tapOnBankPicker() {
+ didTapButton?()
+ }
+}
+
+extension PaymentSecondaryButton {
+ func configure(with configuration: ButtonConfiguration) {
+ contentView.layer.cornerRadius = configuration.cornerRadius
+ contentView.layer.borderWidth = configuration.borderWidth
+ contentView.layer.borderColor = configuration.borderColor.cgColor
+ contentView.backgroundColor = configuration.backgroundColor
+
+ leftImageView.layer.borderColor = configuration.borderColor.cgColor
+ leftImageView.layer.borderWidth = configuration.borderWidth
+ leftImageView.roundCorners(corners: .allCorners, radius: Constants.bankIconCornerRadius)
+
+ titleLabel.textColor = configuration.titleColor
+ if let inputFont = giniHealthConfiguration.textStyleFonts[.input] {
+ titleLabel.font = inputFont
+ }
+ }
+
+ func customConfigure(labelText: String, leftImageIcon: UIImage?, rightImageIcon: String?, rightImageTintColor: UIColor, isPaymentProviderInstalled: Bool, notInstalledTextColor: UIColor) {
+ if let leftImageIcon, isPaymentProviderInstalled {
+ leftImageView.image = leftImageIcon
+ leftImageView.isHidden = false
+ } else {
+ leftImageView.isHidden = true
+ }
+ if let rightImageIcon {
+ rightImageView.image = UIImageNamedPreferred(named: rightImageIcon)?.withRenderingMode(.alwaysTemplate)
+ rightImageView.tintColor = rightImageTintColor
+ rightImageView.isHidden = false
+ } else {
+ rightImageView.isHidden = true
+ }
+ titleLabel.text = labelText
+ if !isPaymentProviderInstalled {
+ titleLabel.textColor = notInstalledTextColor
+ }
+ activateBankImageViewConstraints(isPaymentProviderInstalled: isPaymentProviderInstalled)
+ }
+}
+
+extension PaymentSecondaryButton {
+ enum Constants {
+ static let bankIconSize: CGFloat = 32
+ static let bankIconCornerRadius: CGFloat = 6
+ static let chevronIconSize: CGFloat = 24
+ static let contentTrailingPadding: CGFloat = 16
+ static let bankNameChevronIconPadding: CGFloat = 10
+ static let contentLeadingPadding: CGFloat = 16
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PoweredByGiniView.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PoweredByGiniView.swift
new file mode 100644
index 000000000..88c64a889
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PoweredByGiniView.swift
@@ -0,0 +1,76 @@
+//
+// PoweredByGiniView.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+final class PoweredByGiniView: UIView {
+
+ var viewModel: PoweredByGiniViewModel! {
+ didSet {
+ setupView()
+ }
+ }
+
+ private lazy var poweredByGiniView: UIView = {
+ let view = UIView()
+ view.translatesAutoresizingMaskIntoConstraints = false
+ view.frame = CGRect(x: 0, y: 0, width: .min, height: 22)
+ return view
+ }()
+
+ private lazy var poweredByGiniLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.text = viewModel.poweredByGiniLabelText
+ label.textColor = viewModel.poweredByGiniLabelAccentColor
+ label.font = viewModel.poweredByGiniLabelFont
+ label.numberOfLines = 1
+ label.adjustsFontSizeToFitWidth = true
+ return label
+ }()
+
+ private lazy var giniImageView: UIImageView = {
+ let image = UIImageNamedPreferred(named: viewModel.giniIconName)
+ let imageView = UIImageView(image: image)
+ imageView.frame = CGRect(x: 0, y: 0, width: 28, height: 18)
+ imageView.translatesAutoresizingMaskIntoConstraints = false
+ return imageView
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupView() {
+ self.translatesAutoresizingMaskIntoConstraints = false
+
+ poweredByGiniView.addSubview(poweredByGiniLabel)
+ poweredByGiniView.addSubview(giniImageView)
+ self.frame = CGRect(x: 0, y: 0, width: .min, height: 22)
+ self.addSubview(poweredByGiniView)
+
+ NSLayoutConstraint.activate([
+ poweredByGiniView.trailingAnchor.constraint(equalTo: trailingAnchor),
+ poweredByGiniView.leadingAnchor.constraint(equalTo: leadingAnchor),
+ poweredByGiniView.topAnchor.constraint(equalTo: topAnchor),
+ poweredByGiniView.bottomAnchor.constraint(equalTo: bottomAnchor),
+ poweredByGiniView.trailingAnchor.constraint(equalTo: giniImageView.trailingAnchor),
+ poweredByGiniView.centerYAnchor.constraint(equalTo: giniImageView.centerYAnchor),
+ giniImageView.leadingAnchor.constraint(equalTo: poweredByGiniLabel.trailingAnchor, constant: 4),
+ poweredByGiniLabel.centerYAnchor.constraint(equalTo: poweredByGiniView.centerYAnchor),
+ poweredByGiniLabel.leadingAnchor.constraint(equalTo: poweredByGiniView.leadingAnchor, constant: 0),
+ poweredByGiniLabel.topAnchor.constraint(equalTo: poweredByGiniView.topAnchor, constant: 0),
+ poweredByGiniView.bottomAnchor.constraint(equalTo: poweredByGiniView.bottomAnchor, constant: 0),
+ giniImageView.heightAnchor.constraint(equalToConstant: giniImageView.frame.height),
+ giniImageView.widthAnchor.constraint(equalToConstant: giniImageView.frame.width)
+ ])
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PoweredByGiniViewModel.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PoweredByGiniViewModel.swift
new file mode 100644
index 000000000..90c75846f
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentComponent/PoweredByGiniViewModel.swift
@@ -0,0 +1,22 @@
+//
+// PoweredByGiniViewModel.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+final class PoweredByGiniViewModel {
+
+ // powered by Gini view
+ let poweredByGiniLabelText: String = NSLocalizedStringPreferredFormat("ginihealth.paymentcomponent.poweredByGini.label", comment: "")
+ let poweredByGiniLabelFont: UIFont
+ let poweredByGiniLabelAccentColor: UIColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark4,
+ darkModeColor: UIColor.GiniHealthColors.light4).uiColor()
+ let giniIconName: String = "giniLogo"
+
+ init() {
+ self.poweredByGiniLabelFont = GiniHealthConfiguration.shared.textStyleFonts[.caption2] ?? UIFont.systemFont(ofSize: 12, weight: .regular)
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentInfo.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentInfo.swift
index d612ae748..b6f0aa53d 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentInfo.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentInfo.swift
@@ -2,7 +2,7 @@
// PaymentInfo.swift
// GiniHealth
//
-// Created by Nadya Karaban on 16.04.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import Foundation
@@ -11,10 +11,10 @@ import Foundation
*/
public struct PaymentInfo {
- public var recipient,iban: String
+ public var recipient, iban: String
public var bic: String
public var amount, purpose: String
- public var paymentProviderScheme: String
+ public var paymentUniversalLink: String
public var paymentProviderId: String
}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentReviewModel.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentReviewModel.swift
index 690c0eaa2..6f81c226a 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentReviewModel.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentReviewModel.swift
@@ -2,7 +2,7 @@
// PaymentReviewModer.swift
// GiniHealth
//
-// Created by Nadya Karaban on 18.04.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import GiniHealthAPILibrary
@@ -67,6 +67,10 @@ public class PaymentReviewModel: NSObject {
self.updateImagesLoadingStatus()
}
}
+
+ // Pay invoice label
+ let payInvoiceLabelText: String = NSLocalizedStringPreferredFormat("ginihealth.reviewscreen.banking.app.button.label",
+ comment: "Title label used for the pay invoice button")
public init(with giniHealth: GiniHealth, document: Document, extractions: [Extraction]) {
self.healthSDK = giniHealth
@@ -83,19 +87,6 @@ public class PaymentReviewModel: NSObject {
return PageCollectionCellViewModel(preview: previewImage)
}
- func checkIfAnyPaymentProviderAvailable() {
- healthSDK.checkIfAnyPaymentProviderAvailable {[weak self] result in
- switch result {
- case let .success(providers):
- self?.onPaymentProvidersFetched(providers)
- case let .failure(error):
- if let delegate = self?.healthSDK.delegate, delegate.shouldHandleErrorInternally(error: error) {
- self?.onNoAppsErrorHandling(error)
- }
- }
- }
- }
-
func sendFeedback(updatedExtractions: [Extraction]) {
healthSDK.documentService.submitFeedback(for: document, with: [], and: ["payment": [updatedExtractions]]){ result in
switch result {
@@ -111,7 +102,7 @@ public class PaymentReviewModel: NSObject {
switch result {
case let .success(requestId):
self?.isLoading = false
- self?.openPaymentProviderApp(requestId: requestId, appScheme: paymentInfo.paymentProviderScheme)
+ self?.openPaymentProviderApp(requestId: requestId, universalLink: paymentInfo.paymentUniversalLink)
case let .failure(error):
self?.isLoading = false
if let delegate = self?.healthSDK.delegate, delegate.shouldHandleErrorInternally(error: error) {
@@ -121,8 +112,8 @@ public class PaymentReviewModel: NSObject {
}
}
- func openPaymentProviderApp(requestId: String, appScheme: String) {
- healthSDK.openPaymentProviderApp(requestID: requestId, appScheme: appScheme)
+ func openPaymentProviderApp(requestId: String, universalLink: String) {
+ healthSDK.openPaymentProviderApp(requestID: requestId, universalLink: universalLink)
}
func fetchImages() {
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentReviewViewController.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentReviewViewController.swift
index b82632d0b..472c83d1b 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentReviewViewController.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/PaymentReviewViewController.swift
@@ -2,7 +2,7 @@
// PaymentReviewViewController.swift
// GiniHealth
//
-// Created by Nadya Karaban on 30.03.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
@@ -11,45 +11,45 @@ import GiniHealthAPILibrary
public final class PaymentReviewViewController: UIViewController, UIGestureRecognizerDelegate {
@IBOutlet var pageControl: UIPageControl!
@IBOutlet weak var pageControlHeightConstraint: NSLayoutConstraint!
- @IBOutlet var recipientField: UITextField!
- @IBOutlet var ibanField: UITextField!
- @IBOutlet var amountField: UITextField!
- @IBOutlet var usageField: UITextField!
- @IBOutlet var payButton: GiniCustomButton!
+ @IBOutlet weak var recipientTextFieldView: TextFieldWithLabelView!
+ @IBOutlet weak var ibanTextFieldView: TextFieldWithLabelView!
+ @IBOutlet weak var amountTextFieldView: TextFieldWithLabelView!
+ @IBOutlet weak var usageTextFieldView: TextFieldWithLabelView!
+ @IBOutlet weak var payButtonStackView: UIStackView!
@IBOutlet var paymentInputFieldsErrorLabels: [UILabel]!
@IBOutlet var usageErrorLabel: UILabel!
@IBOutlet var amountErrorLabel: UILabel!
@IBOutlet var ibanErrorLabel: UILabel!
@IBOutlet var recipientErrorLabel: UILabel!
- @IBOutlet var paymentInputFields: [UITextField]!
- @IBOutlet var bankProviderButtonView: UIView!
- @IBOutlet weak var bankProviderLabel: UILabel!
+ @IBOutlet var paymentInputFields: [TextFieldWithLabelView]!
@IBOutlet weak var mainView: UIView!
@IBOutlet var inputContainer: UIView!
@IBOutlet var containerCollectionView: UIView!
@IBOutlet var paymentInfoStackView: UIStackView!
@IBOutlet var collectionView: UICollectionView!
@IBOutlet weak var closeButton: UIButton!
-
- @IBOutlet weak var bankProviderEditIcon: UIImageView!
-
- @IBOutlet weak var bankProviderImage: UIImageView!
@IBOutlet weak var infoBar: UIView!
@IBOutlet weak var infoBarLabel: UILabel!
+ @IBOutlet weak var bottomView: UIView!
var model: PaymentReviewModel?
- var paymentProviders: [PaymentProvider] = []
- private var amountToPay = Price(value: 0, currencyCode: "EUR")
+ private var amountToPay = Price(value: 0, currencyCode: "€")
private var lastValidatedIBAN = ""
+ private var showInfoBarOnce = true
- private var selectedPaymentProvider: PaymentProvider? {
- didSet {
- if let provider = selectedPaymentProvider {
- DispatchQueue.main.async {
- self.updateUIWithDefaultPaymentProvider(provider: provider)
- }
- }
- }
- }
+ private lazy var payInvoiceButton: PaymentPrimaryButton = {
+ let button = PaymentPrimaryButton()
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.frame = CGRect(x: 0, y: 0, width: .greatestFiniteMagnitude, height: Constants.buttonViewHeight)
+ return button
+ }()
+
+ private lazy var poweredByGiniView: PoweredByGiniView = {
+ let view = PoweredByGiniView()
+ view.viewModel = PoweredByGiniViewModel()
+ return view
+ }()
+
+ private var selectedPaymentProvider: PaymentProvider?
public weak var trackingDelegate: GiniHealthTrackingDelegate?
@@ -60,20 +60,22 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
case usageFieldTag
}
- public static func instantiate(with giniHealth: GiniHealth, document: Document, extractions: [Extraction], trackingDelegate: GiniHealthTrackingDelegate? = nil) -> PaymentReviewViewController {
- let vc = (UIStoryboard(name: "PaymentReview", bundle: giniHealthBundle())
+ public static func instantiate(with giniHealth: GiniHealth, document: Document, extractions: [Extraction], selectedPaymentProvider: PaymentProvider?, trackingDelegate: GiniHealthTrackingDelegate? = nil) -> PaymentReviewViewController {
+ let viewController = (UIStoryboard(name: "PaymentReview", bundle: giniHealthBundle())
.instantiateViewController(withIdentifier: "paymentReviewViewController") as? PaymentReviewViewController)!
- vc.model = PaymentReviewModel(with: giniHealth, document: document, extractions: extractions )
- vc.trackingDelegate = trackingDelegate
- return vc
+ viewController.model = PaymentReviewModel(with: giniHealth, document: document, extractions: extractions)
+ viewController.trackingDelegate = trackingDelegate
+ viewController.selectedPaymentProvider = selectedPaymentProvider
+ return viewController
}
- public static func instantiate(with giniHealth: GiniHealth, data: DataForReview, trackingDelegate: GiniHealthTrackingDelegate? = nil) -> PaymentReviewViewController {
- let vc = (UIStoryboard(name: "PaymentReview", bundle: giniHealthBundle())
+ public static func instantiate(with giniHealth: GiniHealth, data: DataForReview, selectedPaymentProvider: PaymentProvider?, trackingDelegate: GiniHealthTrackingDelegate? = nil) -> PaymentReviewViewController {
+ let viewController = (UIStoryboard(name: "PaymentReview", bundle: giniHealthBundle())
.instantiateViewController(withIdentifier: "paymentReviewViewController") as? PaymentReviewViewController)!
- vc.model = PaymentReviewModel(with: giniHealth, document: data.document, extractions: data.extractions)
- vc.trackingDelegate = trackingDelegate
- return vc
+ viewController.model = PaymentReviewModel(with: giniHealth, document: data.document, extractions: data.extractions)
+ viewController.trackingDelegate = trackingDelegate
+ viewController.selectedPaymentProvider = selectedPaymentProvider
+ return viewController
}
var giniHealthConfiguration = GiniHealthConfiguration.shared
@@ -88,43 +90,26 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
- showInfoBar()
+ if showInfoBarOnce {
+ showInfoBar()
+ showInfoBarOnce = false
+ }
}
fileprivate func setupViewModel() {
-
- model?.onNoAppsErrorHandling = {[weak self] error in
- DispatchQueue.main.async {
- self?.showError(message: NSLocalizedStringPreferredFormat("ginihealth.errors.no.banking.app.installed",
- comment: "no bank apps installed") )
- }
- }
-
model?.onExtractionFetched = { [weak self] () in
DispatchQueue.main.async {
self?.fillInInputFields()
}
}
- model?.onPaymentProvidersFetched = {[weak self] providers in
- DispatchQueue.main.async { [weak self] in
- self?.paymentProviders.append(contentsOf: providers)
- if let paymentProviders = self?.paymentProviders, paymentProviders.count > 0 {
- let providerId = UserDefaults.standard.string(forKey: "ginihealth.defaultPaymentProviderId")
- let provider = paymentProviders.first(where: { $0.id == providerId }) ?? paymentProviders[0]
- self?.selectedPaymentProvider = provider
- }
- }
- }
-
- model?.checkIfAnyPaymentProviderAvailable()
-
-
model?.updateImagesLoadingStatus = { [weak self] () in
DispatchQueue.main.async { [weak self] in
let isLoading = self?.model?.isImagesLoading ?? false
if isLoading {
- self?.collectionView.showLoading(style: self?.giniHealthConfiguration.loadingIndicatorStyle, color: UIColor.from(giniColor: self?.giniHealthConfiguration.loadingIndicatorColor ?? GiniHealthConfiguration.shared.loadingIndicatorColor), scale: self?.giniHealthConfiguration.loadingIndicatorScale)
+ self?.collectionView.showLoading(style: .whiteLarge,
+ color: UIColor.GiniHealthColors.accent1,
+ scale: Constants.loadingIndicatorScale)
} else {
self?.collectionView.stopLoading()
}
@@ -135,7 +120,15 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
DispatchQueue.main.async { [weak self] in
let isLoading = self?.model?.isLoading ?? false
if isLoading {
- self?.view.showLoading(style: self?.giniHealthConfiguration.loadingIndicatorStyle, color: UIColor.from(giniColor: self?.giniHealthConfiguration.loadingIndicatorColor ?? GiniHealthConfiguration.shared.loadingIndicatorColor), scale: self?.giniHealthConfiguration.loadingIndicatorScale)
+ if #available(iOS 13.0, *) {
+ self?.view.showLoading(style: Constants.loadingIndicatorStyle,
+ color: UIColor.GiniHealthColors.accent1,
+ scale: Constants.loadingIndicatorScale)
+ } else {
+ self?.view.showLoading(style: .whiteLarge,
+ color: UIColor.GiniHealthColors.accent1,
+ scale: Constants.loadingIndicatorScale)
+ }
} else {
self?.view.stopLoading()
}
@@ -161,12 +154,6 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
}
}
- model?.onBankSelection = {[weak self] provider in
- DispatchQueue.main.async {
- self?.updateUIWithDefaultPaymentProvider(provider: provider)
- }
- }
-
model?.onCreatePaymentRequestErrorHandling = {[weak self] () in
DispatchQueue.main.async {
self?.showError(message: NSLocalizedStringPreferredFormat("ginihealth.errors.failed.payment.request.creation",
@@ -183,7 +170,7 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
override public func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
- inputContainer.roundCorners(corners: [.topLeft, .topRight], radius: 12)
+ inputContainer.roundCorners(corners: [.topLeft, .topRight], radius: Constants.cornerRadiusInputContainer)
}
public override var preferredStatusBarStyle: UIStatusBarStyle {
@@ -199,18 +186,19 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
configurePageControl()
configureCloseButton()
configurePayButtonInitialState()
+ configurePoweredByGiniView()
hideErrorLabels()
fillInInputFields()
- addDoneButtonForNumPad(amountField)
+ addDoneButtonForNumPad(amountTextFieldView)
}
// MARK: - Info bar
fileprivate func configureInfoBar() {
- infoBar.roundCorners(corners: [.topLeft, .topRight], radius: giniHealthConfiguration.infoBarCornerRadius)
- infoBar.backgroundColor = UIColor.from(giniColor: giniHealthConfiguration.infoBarBackgroundColor)
- infoBarLabel.textColor = UIColor.from(giniColor: giniHealthConfiguration.infoBarTextColor)
- infoBarLabel.font = giniHealthConfiguration.customFont.regular
+ infoBar.roundCorners(corners: [.topLeft, .topRight], radius: Constants.cornerRadiusInfoBar)
+ infoBar.backgroundColor = UIColor.GiniHealthColors.success1
+ infoBarLabel.textColor = UIColor.GiniHealthColors.dark7
+ infoBarLabel.font = giniHealthConfiguration.textStyleFonts[.caption1]
infoBarLabel.adjustsFontForContentSizeCategory = true
infoBarLabel.text = NSLocalizedStringPreferredFormat("ginihealth.reviewscreen.infobar.message",
comment: "info bar message")
@@ -220,11 +208,11 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
configureInfoBar()
infoBar.isHidden = false
let screenSize = UIScreen.main.bounds.size
- UIView.animate(withDuration: 0.5,
+ UIView.animate(withDuration: Constants.animationDuration,
delay: 0, usingSpringWithDamping: 1.0,
initialSpringVelocity: 1.0,
options: [], animations: {
- self.infoBar.frame = CGRect(x: 0, y: self.inputContainer.frame.minY + self.giniHealthConfiguration.infoBarCornerRadius - self.infoBar.frame.height, width: screenSize.width, height: self.infoBar.frame.height)
+ self.infoBar.frame = CGRect(x: 0, y: self.inputContainer.frame.minY + Constants.moveHeightInfoBar - self.infoBar.frame.height, width: screenSize.width, height: self.infoBar.frame.height)
}, completion: nil)
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.animateSlideDownInfoBar()
@@ -233,7 +221,7 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
fileprivate func animateSlideDownInfoBar() {
let screenSize = UIScreen.main.bounds.size
- UIView.animate(withDuration: 0.5,
+ UIView.animate(withDuration: Constants.animationDuration,
delay: 0, usingSpringWithDamping: 1.0,
initialSpringVelocity: 1.0,
options: [], animations: {
@@ -242,73 +230,19 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
self.infoBar.isHidden = true
})
}
-
- // MARK: - ConfigureBankProviderView
-
- fileprivate func configureBankProviderView(paymentProvider: PaymentProvider) {
- bankProviderButtonView.backgroundColor = UIColor.from(giniColor: giniHealthConfiguration.bankButtonBackgroundColor)
- bankProviderButtonView.layer.cornerRadius = giniHealthConfiguration.bankButtonCornerRadius
- bankProviderButtonView.layer.borderWidth = giniHealthConfiguration.bankButtonBorderWidth
- bankProviderButtonView.layer.borderColor = UIColor.from(giniColor: giniHealthConfiguration.bankButtonBorderColor).cgColor
-
- bankProviderLabel.textColor = UIColor.from(giniColor: giniHealthConfiguration.bankButtonTextColor)
- bankProviderLabel.text = paymentProvider.name
-
- let imageData = paymentProvider.iconData
- if let image = UIImage(data: imageData){
- bankProviderImage.image = image
- }
-
- if let templateImage = UIImageNamedPreferred(named: "editIcon"), self.paymentProviders.count > 1 {
- bankProviderEditIcon.image = templateImage.withRenderingMode(.alwaysTemplate)
- bankProviderEditIcon.tintColor = UIColor.from(giniColor: giniHealthConfiguration.bankButtonEditIconColor)
- let selectProviderTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(selectBankProviderTapped))
- bankProviderButtonView.addGestureRecognizer(selectProviderTapRecognizer)
- }
- bankProviderLabel.font = giniHealthConfiguration.customFont.regular
- bankProviderLabel.adjustsFontForContentSizeCategory = true
-
- }
-
- fileprivate func updateUIWithDefaultPaymentProvider(provider: PaymentProvider){
- self.configureBankProviderView(paymentProvider: provider)
- self.configurePayButton(paymentProvider: provider)
- }
-
- fileprivate func presentBankSelectionViewController() {
-
- let availableProviders = self.paymentProviders
- if availableProviders.count > 1 {
- let bankSelectionViewController = BankProviderViewController.instantiate(with: availableProviders)
- bankSelectionViewController.onSelectedProviderDidChanged = { provider in
- self.selectedPaymentProvider = provider
- }
- bankSelectionViewController.modalPresentationStyle = .overCurrentContext
- bankSelectionViewController.modalTransitionStyle = .crossDissolve
- present(bankSelectionViewController, animated: true)
- }
- }
-
- fileprivate func configurePayButton(paymentProvider: PaymentProvider) {
- let backgroundColorString = String.rgbaHexFrom(rgbHex: paymentProvider.colors.background)
- if let backgroundHexColor = UIColor(hex: backgroundColorString) {
- payButton.defaultBackgroundColor = UIColor.from(giniColor: GiniColor(lightModeColor: backgroundHexColor, darkModeColor: backgroundHexColor))
- }
- let textColorString = String.rgbaHexFrom(rgbHex: paymentProvider.colors.text)
- if let textHexColor = UIColor(hex: textColorString) {
- payButton.textColor = UIColor.from(giniColor: GiniColor(lightModeColor: textHexColor, darkModeColor: textHexColor))
- }
- disablePayButtonIfNeeded()
- }
fileprivate func configurePayButtonInitialState() {
- payButton.disabledBackgroundColor = UIColor.from(giniColor: giniHealthConfiguration.payButtonDisabledBackgroundColor)
- payButton.isEnabled = false
- payButton.disabledTextColor = UIColor.from(giniColor: giniHealthConfiguration.payButtonDisabledTextColor)
- payButton.layer.cornerRadius = giniHealthConfiguration.payButtonCornerRadius
- payButton.titleLabel?.font = giniHealthConfiguration.payButtonTitleFont
- payButton.setTitle( NSLocalizedStringPreferredFormat("ginihealth.reviewscreen.next.button.title",
- comment: "next button title"), for: .normal)
+ payButtonStackView.addArrangedSubview(payInvoiceButton)
+ guard let model else { return }
+ payInvoiceButton.configure(with: giniHealthConfiguration.primaryButtonConfiguration)
+ payInvoiceButton.customConfigure(paymentProviderColors: selectedPaymentProvider?.colors,
+ isPaymentProviderInstalled: true,
+ text: model.payInvoiceLabelText,
+ leftImageData: selectedPaymentProvider?.iconData)
+ disablePayButtonIfNeeded()
+ payInvoiceButton.didTapButton = { [weak self] in
+ self?.payButtonClicked()
+ }
}
fileprivate func configurePaymentInputFields() {
@@ -323,15 +257,17 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
}
fileprivate func configurePageControl() {
- pageControl.layer.zPosition = 10
- pageControl.pageIndicatorTintColor = UIColor.from(giniColor:giniHealthConfiguration.pageIndicatorTintColor)
- pageControl.currentPageIndicatorTintColor = UIColor.from(giniColor:giniHealthConfiguration.currentPageIndicatorTintColor)
+ pageControl.layer.zPosition = Constants.zPositionPageControl
+ pageControl.pageIndicatorTintColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark4,
+ darkModeColor: UIColor.GiniHealthColors.light4).uiColor()
+ pageControl.currentPageIndicatorTintColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark2,
+ darkModeColor: UIColor.GiniHealthColors.light5).uiColor()
pageControl.hidesForSinglePage = true
pageControl.numberOfPages = model?.document.pageCount ?? 1
if pageControl.numberOfPages == 1 {
pageControlHeightConstraint.constant = 0
} else {
- pageControlHeightConstraint.constant = 20
+ pageControlHeightConstraint.constant = Constants.heightPageControl
}
}
@@ -341,72 +277,78 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
}
fileprivate func configureScreenBackgroundColor() {
- let screenBackgroundColor = UIColor.from(giniColor:giniHealthConfiguration.paymentScreenBackgroundColor)
+ let screenBackgroundColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.light7,
+ darkModeColor: UIColor.GiniHealthColors.light7).uiColor()
mainView.backgroundColor = screenBackgroundColor
collectionView.backgroundColor = screenBackgroundColor
pageControl.backgroundColor = screenBackgroundColor
- inputContainer.backgroundColor = UIColor.from(giniColor:giniHealthConfiguration.inputFieldsContainerBackgroundColor)
+ inputContainer.backgroundColor = GiniColor(lightModeColor: UIColor.GiniHealthColors.dark7,
+ darkModeColor: UIColor.GiniHealthColors.light7).uiColor()
+ }
+
+ fileprivate func configurePoweredByGiniView() {
+ bottomView.addSubview(poweredByGiniView)
+ setupPoweredByGiniConstraints()
+ }
+
+ private func setupPoweredByGiniConstraints() {
+ let poweredByGiniBottomAnchorConstraint = poweredByGiniView.bottomAnchor.constraint(equalTo: poweredByGiniView.bottomAnchor)
+ poweredByGiniBottomAnchorConstraint.priority = .required - 1
+ NSLayoutConstraint.activate([
+ poweredByGiniView.topAnchor.constraint(equalTo: bottomView.topAnchor),
+ poweredByGiniView.heightAnchor.constraint(equalToConstant: poweredByGiniView.frame.height),
+ poweredByGiniView.trailingAnchor.constraint(equalTo: bottomView.trailingAnchor),
+ poweredByGiniBottomAnchorConstraint
+ ])
}
// MARK: - Input fields configuration
- fileprivate func applyDefaultStyle(_ field: UITextField) {
- field.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: field.frame.height))
- field.leftViewMode = .always
- field.rightView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: field.frame.height))
- field.rightViewMode = .always
- field.layer.cornerRadius = self.giniHealthConfiguration.paymentInputFieldCornerRadius
- field.layer.borderWidth = giniHealthConfiguration.paymentInputFieldBorderWidth
- field.backgroundColor = UIColor.from(giniColor: giniHealthConfiguration.paymentInputFieldBackgroundColor)
- field.font = giniHealthConfiguration.customFont.regular
- field.textColor = UIColor.from(giniColor: giniHealthConfiguration.paymentInputFieldTextColor)
- let placeholderText = inputFieldPlaceholderText(field)
- field.attributedPlaceholder = NSAttributedString(string: placeholderText, attributes: [NSAttributedString.Key.foregroundColor: UIColor.from(giniColor: giniHealthConfiguration.paymentInputFieldPlaceholderTextColor), NSAttributedString.Key.font: giniHealthConfiguration.customFont.regular])
- field.layer.masksToBounds = true
+ fileprivate func applyDefaultStyle(_ textFieldView: TextFieldWithLabelView) {
+ textFieldView.configure(configuration: giniHealthConfiguration.defaultStyleInputFieldConfiguration)
+ textFieldView.customConfigure(labelTitle: inputFieldPlaceholderText(textFieldView))
+ textFieldView.textField.delegate = self
+ textFieldView.textField.tag = textFieldView.tag
+ textFieldView.layer.masksToBounds = true
}
- fileprivate func applyErrorStyle(_ textField: UITextField) {
- UIView.animate(withDuration: 0.3) {
- textField.layer.cornerRadius = self.giniHealthConfiguration.paymentInputFieldCornerRadius
- textField.backgroundColor = UIColor.from(giniColor: self.giniHealthConfiguration.paymentInputFieldBackgroundColor)
- textField.layer.borderWidth = self.giniHealthConfiguration.paymentInputFieldErrorStyleBorderWidth
- textField.layer.borderColor = UIColor.from(giniColor: self.giniHealthConfiguration.paymentInputFieldErrorStyleColor).cgColor
- textField.layer.masksToBounds = true
+ fileprivate func applyErrorStyle(_ textFieldView: TextFieldWithLabelView) {
+ UIView.animate(withDuration: Constants.animationDuration) {
+ textFieldView.configure(configuration: self.giniHealthConfiguration.errorStyleInputFieldConfiguration)
+ textFieldView.layer.masksToBounds = true
}
}
- fileprivate func applySelectionStyle(_ textField: UITextField) {
- UIView.animate(withDuration: 0.3) {
- textField.layer.cornerRadius = self.giniHealthConfiguration.paymentInputFieldCornerRadius
- textField.backgroundColor = UIColor.from(giniColor: self.giniHealthConfiguration.paymentInputFieldSelectionBackgroundColor)
- textField.layer.borderWidth = self.giniHealthConfiguration.paymentInputFieldSelectionStyleBorderWidth
- textField.layer.borderColor = UIColor.from(giniColor: self.giniHealthConfiguration.paymentInputFieldSelectionStyleColor).cgColor
- textField.layer.masksToBounds = true
+ fileprivate func applySelectionStyle(_ textFieldView: TextFieldWithLabelView) {
+ UIView.animate(withDuration: Constants.animationDuration) { [self] in
+ textFieldView.configure(configuration: self.giniHealthConfiguration.selectionStyleInputFieldConfiguration)
+ textFieldView.layer.masksToBounds = true
}
}
-
+
@objc fileprivate func doneWithAmountInputButtonTapped() {
- amountField.endEditing(true)
- amountField.resignFirstResponder()
+ amountTextFieldView.textField.endEditing(true)
+ amountTextFieldView.textField.resignFirstResponder()
- if amountField.hasText && !amountField.isReallyEmpty {
+ if amountTextFieldView.textField.hasText && !amountTextFieldView.textField.isReallyEmpty {
updateAmoutToPayWithCurrencyFormat()
}
}
- func addDoneButtonForNumPad(_ textField: UITextField) {
- let toolbarDone = UIToolbar(frame:CGRect(x:0, y:0, width:view.frame.width, height:40))
- toolbarDone.sizeToFit()
- let flexBarButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
- let barBtnDone = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonItem.SystemItem.done,
- target: self, action: #selector(PaymentReviewViewController.doneWithAmountInputButtonTapped))
+ func addDoneButtonForNumPad(_ textFieldView: TextFieldWithLabelView) {
+ let toolbarDone = UIToolbar(frame:CGRect(x: 0, y: 0, width: view.frame.width, height: Constants.heightToolbar))
+ toolbarDone.sizeToFit()
+ let flexBarButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
+ let barBtnDone = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonItem.SystemItem.done,
+ target: self,
+ action: #selector(PaymentReviewViewController.doneWithAmountInputButtonTapped))
- toolbarDone.items = [flexBarButton, barBtnDone]
- textField.inputAccessoryView = toolbarDone
+ toolbarDone.items = [flexBarButton, barBtnDone]
+ textFieldView.textField.inputAccessoryView = toolbarDone
}
- fileprivate func inputFieldPlaceholderText(_ textField: UITextField) -> String {
- if let fieldIdentifier = TextFieldType(rawValue: textField.tag) {
+ fileprivate func inputFieldPlaceholderText(_ textFieldView: TextFieldWithLabelView) -> String {
+ if let fieldIdentifier = TextFieldType(rawValue: textFieldView.tag) {
switch fieldIdentifier {
case .recipientFieldTag:
return NSLocalizedStringPreferredFormat("ginihealth.reviewscreen.recipient.placeholder",
@@ -431,64 +373,69 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
disablePayButtonIfNeeded()
}
- fileprivate func validateTextField(_ textField: UITextField) {
- if let fieldIdentifier = TextFieldType(rawValue: textField.tag) {
+ fileprivate func validateTextField(_ textFieldViewTag: Int) {
+ let textFieldView = textFieldViewWithTag(tag: textFieldViewTag)
+ if let fieldIdentifier = TextFieldType(rawValue: textFieldViewTag) {
switch fieldIdentifier {
case .amountFieldTag:
- if amountField.hasText && !amountField.isReallyEmpty {
+ if amountTextFieldView.textField.hasText && !amountTextFieldView.textField.isReallyEmpty {
let decimalPart = amountToPay.value
if decimalPart > 0 {
- applyDefaultStyle(textField)
+ applyDefaultStyle(textFieldView)
hideErrorLabel(textFieldTag: fieldIdentifier)
} else {
- amountField.text = ""
- applyErrorStyle(textField)
+ amountTextFieldView.text = ""
+ applyErrorStyle(textFieldView)
showErrorLabel(textFieldTag: fieldIdentifier)
}
} else {
- applyErrorStyle(textField)
+ applyErrorStyle(textFieldView)
showErrorLabel(textFieldTag: fieldIdentifier)
}
case .ibanFieldTag, .recipientFieldTag, .usageFieldTag:
- if textField.hasText && !textField.isReallyEmpty {
- applyDefaultStyle(textField)
+ if textFieldView.textField.hasText && !textFieldView.textField.isReallyEmpty {
+ applyDefaultStyle(textFieldView)
hideErrorLabel(textFieldTag: fieldIdentifier)
} else {
- applyErrorStyle(textField)
+ applyErrorStyle(textFieldView)
showErrorLabel(textFieldTag: fieldIdentifier)
}
}
}
}
+ fileprivate func textFieldViewWithTag(tag: Int) -> TextFieldWithLabelView {
+ paymentInputFields.first(where: { $0.tag == tag }) ?? TextFieldWithLabelView()
+ }
+
fileprivate func validateIBANTextField(){
- if let ibanText = ibanField.text, ibanField.hasText {
+ if let ibanText = ibanTextFieldView.textField.text, ibanTextFieldView.textField.hasText {
if IBANValidator().isValid(iban: ibanText) {
- applyDefaultStyle(ibanField)
+ applyDefaultStyle(ibanTextFieldView)
hideErrorLabel(textFieldTag: .ibanFieldTag)
} else {
- applyErrorStyle(ibanField)
+ applyErrorStyle(ibanTextFieldView)
showValidationErrorLabel(textFieldTag: .ibanFieldTag)
}
} else {
- applyErrorStyle(ibanField)
+ applyErrorStyle(ibanTextFieldView)
showErrorLabel(textFieldTag: .ibanFieldTag)
}
}
fileprivate func showIBANValidationErrorIfNeeded(){
if IBANValidator().isValid(iban: lastValidatedIBAN) {
- applyDefaultStyle(ibanField)
+ applyDefaultStyle(ibanTextFieldView)
hideErrorLabel(textFieldTag: .ibanFieldTag)
} else {
- applyErrorStyle(ibanField)
+ applyErrorStyle(ibanTextFieldView)
showValidationErrorLabel(textFieldTag: .ibanFieldTag)
}
}
fileprivate func validateAllInputFields() {
for textField in paymentInputFields {
- validateTextField(textField)
+ validateTextField(textField.tag)
}
}
@@ -499,20 +446,21 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
}
fileprivate func fillInInputFields() {
- recipientField.text = model?.extractions.first(where: {$0.name == "payment_recipient"})?.value
- ibanField.text = model?.extractions.first(where: {$0.name == "iban"})?.value
- usageField.text = model?.extractions.first(where: {$0.name == "payment_purpose"})?.value
- if let amountString = model?.extractions.first(where: {$0.name == "amount_to_pay"})?.value, let amountToPay = Price(extractionString: amountString) {
+ guard let model else { return }
+ recipientTextFieldView.text = model.extractions.first(where: {$0.name == "payment_recipient"})?.value
+ ibanTextFieldView.text = model.extractions.first(where: {$0.name == "iban"})?.value
+ usageTextFieldView.text = model.extractions.first(where: {$0.name == "payment_purpose"})?.value
+ if let amountString = model.extractions.first(where: {$0.name == "amount_to_pay"})?.value, let amountToPay = Price(extractionString: amountString) {
self.amountToPay = amountToPay
let amountToPayText = amountToPay.string
- amountField.text = amountToPayText
+ amountTextFieldView.text = amountToPayText
}
validateAllInputFields()
disablePayButtonIfNeeded()
}
fileprivate func disablePayButtonIfNeeded() {
- payButton.isEnabled = paymentInputFields.allSatisfy { !$0.isReallyEmpty } && !paymentProviders.isEmpty && amountToPay.value > 0
+ payInvoiceButton.superview?.alpha = paymentInputFields.allSatisfy({ !$0.textField.isReallyEmpty }) && amountToPay.value > 0 ? 1 : 0.4
}
@@ -539,7 +487,7 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
}
if errorLabel.isHidden {
errorLabel.isHidden = false
- errorLabel.textColor = UIColor.from(giniColor: giniHealthConfiguration.paymentInputFieldErrorStyleColor)
+ errorLabel.textColor = UIColor.GiniHealthColors.feedback1
errorLabel.text = errorMessage
}
}
@@ -562,7 +510,7 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
}
if errorLabel.isHidden {
errorLabel.isHidden = false
- errorLabel.textColor = UIColor.from(giniColor: giniHealthConfiguration.paymentInputFieldErrorStyleColor)
+ errorLabel.textColor = UIColor.GiniHealthColors.feedback1
errorLabel.text = errorMessage
}
}
@@ -585,50 +533,51 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
disablePayButtonIfNeeded()
}
- // MARK: - IBAction
-
- @objc func selectBankProviderTapped() {
- var event = TrackingEvent.init(type: PaymentReviewScreenEventType.onBankSelectionButtonClicked)
- if let selectedPaymentProviderName = selectedPaymentProvider?.name {
- event.info = ["paymentProvider" : selectedPaymentProviderName]
- }
- trackingDelegate?.onPaymentReviewScreenEvent(event: event)
- bankProviderButtonView.alpha = 0.5
- UIView.animate(withDuration: 0.5) {
- self.bankProviderButtonView.alpha = 1.0
- }
- presentBankSelectionViewController()
- }
-
- @IBAction func payButtonClicked(_ sender: Any) {
- var event = TrackingEvent.init(type: PaymentReviewScreenEventType.onNextButtonClicked)
+ // MARK: - Pay Button Action
+ func payButtonClicked() {
+ var event = TrackingEvent.init(type: PaymentReviewScreenEventType.onToTheBankButtonClicked)
if let selectedPaymentProviderName = selectedPaymentProvider?.name {
- event.info = ["paymentProvider" : selectedPaymentProviderName]
+ event.info = ["paymentProvider": selectedPaymentProviderName]
}
trackingDelegate?.onPaymentReviewScreenEvent(event: event)
view.endEditing(true)
validateAllInputFields()
validateIBANTextField()
- if let iban = ibanField.text {
+ if let iban = ibanTextFieldView.text {
lastValidatedIBAN = iban
}
// check if no errors labels are shown
if (paymentInputFieldsErrorLabels.allSatisfy { $0.isHidden }) {
-
- // check for the 1st run where no provider where selected and saved yet
- if self.selectedPaymentProvider == nil {
- self.selectedPaymentProvider = paymentProviders.first
- }
- if let selectedProvider = selectedPaymentProvider, !amountField.isReallyEmpty
- {
+ if let selectedProvider = selectedPaymentProvider, !amountTextFieldView.textField.isReallyEmpty {
let amountText = amountToPay.extractionString
- let paymentInfo = PaymentInfo(recipient: recipientField.text ?? "", iban: ibanField.text ?? "", bic: "", amount: amountText, purpose: usageField.text ?? "", paymentProviderScheme: selectedProvider.appSchemeIOS, paymentProviderId: selectedProvider.id)
+ let paymentInfo = PaymentInfo(recipient: recipientTextFieldView.text ?? "",
+ iban: ibanTextFieldView.text ?? "",
+ bic: "", amount: amountText,
+ purpose: usageTextFieldView.text ?? "",
+ paymentUniversalLink: selectedProvider.universalLinkIOS,
+ paymentProviderId: selectedProvider.id)
model?.createPaymentRequest(paymentInfo: paymentInfo)
- let paymentRecipientExtraction = Extraction(box: nil, candidates: "", entity: "text", value: recipientField.text ?? "", name: "payment_recipient")
- let ibanExtraction = Extraction(box: nil, candidates: "", entity: "iban", value: paymentInfo.iban, name: "iban")
- let referenceExtraction = Extraction(box: nil, candidates: "", entity: "text", value: paymentInfo.purpose, name: "payment_purpose")
- let amoutToPayExtraction = Extraction(box: nil, candidates: "", entity: "amount", value: paymentInfo.amount, name: "amount_to_pay")
+ let paymentRecipientExtraction = Extraction(box: nil,
+ candidates: "",
+ entity: "text",
+ value: recipientTextFieldView.text ?? "",
+ name: "payment_recipient")
+ let ibanExtraction = Extraction(box: nil,
+ candidates: "",
+ entity: "iban",
+ value: paymentInfo.iban,
+ name: "iban")
+ let referenceExtraction = Extraction(box: nil,
+ candidates: "",
+ entity: "text", value:
+ paymentInfo.purpose,
+ name: "payment_purpose")
+ let amoutToPayExtraction = Extraction(box: nil,
+ candidates: "",
+ entity: "amount", value:
+ paymentInfo.amount,
+ name: "amount_to_pay")
let updatedExtractions = [paymentRecipientExtraction, ibanExtraction, referenceExtraction, amoutToPayExtraction]
model?.sendFeedback(updatedExtractions: updatedExtractions)
}
@@ -669,7 +618,7 @@ public final class PaymentReviewViewController: UIViewController, UIGestureRecog
}
@objc func keyboardWillHide(notification: NSNotification) {
- let animationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double ?? 0.3
+ let animationDuration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double ?? Constants.animationDuration
let animationCurve = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt ?? UInt(UIView.AnimationCurve.easeOut.rawValue)
self.keyboardWillShowCalled = false
@@ -736,26 +685,38 @@ extension PaymentReviewViewController: UITextFieldDelegate {
Updates amoutToPay, formated string with a currency and removes "0.00" value
*/
fileprivate func updateAmoutToPayWithCurrencyFormat() {
- if amountField.hasText, let amountFieldText = amountField.text {
+ if amountTextFieldView.textField.hasText, let amountFieldText = amountTextFieldView.text {
if let priceValue = decimal(from: amountFieldText ) {
amountToPay.value = priceValue
if priceValue > 0 {
let amountToPayText = amountToPay.string
- amountField.text = amountToPayText
+ amountTextFieldView.text = amountToPayText
} else {
- amountField.text = ""
+ amountTextFieldView.text = ""
}
}
}
}
+ public func textFieldDidBeginEditing(_ textField: UITextField) {
+ applySelectionStyle(textFieldViewWithTag(tag: textField.tag))
+
+ // remove currency symbol and whitespaces for edit mode
+ if let fieldIdentifier = TextFieldType(rawValue: textField.tag) {
+ hideErrorLabel(textFieldTag: fieldIdentifier)
+
+ if fieldIdentifier == .amountFieldTag {
+ let amountToPayText = amountToPay.stringWithoutSymbol
+ amountTextFieldView.text = amountToPayText
+ }
+ }
+ }
public func textFieldDidEndEditing(_ textField: UITextField) {
-
// add currency format when edit is finished
if TextFieldType(rawValue: textField.tag) == .amountFieldTag {
updateAmoutToPayWithCurrencyFormat()
}
- validateTextField(textField)
+ validateTextField(textField.tag)
if TextFieldType(rawValue: textField.tag) == .ibanFieldTag {
if textField.text == lastValidatedIBAN {
showIBANValidationErrorIfNeeded()
@@ -763,20 +724,6 @@ extension PaymentReviewViewController: UITextFieldDelegate {
}
disablePayButtonIfNeeded()
}
-
- public func textFieldDidBeginEditing(_ textField: UITextField) {
- applySelectionStyle(textField)
-
- // remove currency symbol and whitespaces for edit mode
- if let fieldIdentifier = TextFieldType(rawValue: textField.tag) {
- hideErrorLabel(textFieldTag: fieldIdentifier)
-
- if fieldIdentifier == .amountFieldTag {
- let amountToPayText = amountToPay.stringWithoutSymbol
- amountField.text = amountToPayText
- }
- }
- }
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if TextFieldType(rawValue: textField.tag) == .amountFieldTag,
@@ -827,7 +774,7 @@ extension PaymentReviewViewController: UICollectionViewDelegate, UICollectionVie
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "pageCellIdentifier", for: indexPath) as! PageCollectionViewCell
cell.pageImageView.frame = CGRect(x: 0, y: 0, width: collectionView.frame.width, height: collectionView.frame.height)
- cell.pageImageView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 20.0, right: 0.0)
+ cell.pageImageView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: Constants.bottomPaddingPageImageView, right: 0.0)
let cellModel = model?.getCellViewModel(at: indexPath)
cell.pageImageView.display(image: cellModel?.preview ?? UIImage())
return cell
@@ -859,3 +806,20 @@ extension PaymentReviewViewController {
present(alertController, animated: true, completion: nil)
}
}
+
+extension PaymentReviewViewController {
+ private enum Constants {
+ static let buttonViewHeight: CGFloat = 56
+ static let animationDuration: CGFloat = 0.3
+ static let cornerRadiusInputContainer = 12.0
+ static let cornerRadiusInfoBar = 12.0
+ static let moveHeightInfoBar = 32.0
+ static let zPositionPageControl = 10.0
+ static let heightPageControl = 20.0
+ static let heightToolbar = 40.0
+ static let bottomPaddingPageImageView = 20.0
+ static let loadingIndicatorScale = 1.0
+ @available(iOS 13.0, *)
+ static let loadingIndicatorStyle = UIActivityIndicatorView.Style.large
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/String+Utils.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/String+Utils.swift
deleted file mode 100644
index cb8efce99..000000000
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/String+Utils.swift
+++ /dev/null
@@ -1,19 +0,0 @@
-//
-// String+Utils.swift
-// GiniHealth
-//
-// Created by Nadya Karaban on 19.05.21.
-//
-
-import Foundation
-public extension String {
- var numberValue: NSNumber? {
- let formatter = NumberFormatter()
- formatter.numberStyle = .decimal
- return formatter.number(from: self)
- }
-
- static func rgbaHexFrom(rgbHex: String) -> String {
- return "#\(rgbHex)FF"
- }
-}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/TextFieldConfiguration.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/TextFieldConfiguration.swift
new file mode 100644
index 000000000..3abb80675
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/TextFieldConfiguration.swift
@@ -0,0 +1,40 @@
+//
+// TextFieldConfiguration.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+public struct TextFieldConfiguration {
+ let backgroundColor: UIColor
+ let borderColor: UIColor
+ let textColor: UIColor
+ let cornerRadius: CGFloat
+ let borderWidth: CGFloat
+ let placeholderForegroundColor: UIColor
+
+
+ /// Text Field configuration initalizer
+ /// - Parameters:
+ /// - backgroundColor: the textField's background color
+ /// - borderColor: the textField's border color
+ /// - textColor: the textField's text color
+ /// - cornerRadius: the textField's corner radius
+ /// - borderWidth: the textField's border width
+ /// - placeholderForegroundColor:the textField's placeholder foreground color
+
+ public init(backgroundColor: UIColor,
+ borderColor: UIColor,
+ textColor: UIColor,
+ cornerRadius: CGFloat,
+ borderWidth: CGFloat,
+ placeholderForegroundColor: UIColor) {
+ self.backgroundColor = backgroundColor
+ self.borderColor = borderColor
+ self.textColor = textColor
+ self.cornerRadius = cornerRadius
+ self.borderWidth = borderWidth
+ self.placeholderForegroundColor = placeholderForegroundColor
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/TextFieldWithLabelView.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/TextFieldWithLabelView.swift
new file mode 100644
index 000000000..e9ed9a325
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/TextFieldWithLabelView.swift
@@ -0,0 +1,90 @@
+//
+// TextFieldWithLabelView.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+import UIKit
+
+final class TextFieldWithLabelView: UIView {
+ private lazy var configuration = GiniHealthConfiguration.shared
+
+ var text: String? {
+ get {
+ return textField.text
+ }
+ set {
+ textField.text = newValue
+ textField.accessibilityValue = newValue
+ }
+ }
+
+ private lazy var titleLabel: UILabel = {
+ let label = UILabel()
+ label.translatesAutoresizingMaskIntoConstraints = false
+ label.font = configuration.textStyleFonts[.caption2]
+ label.adjustsFontForContentSizeCategory = true
+ return label
+ }()
+
+ lazy var textField: UITextField = {
+ let textField = UITextField()
+ textField.translatesAutoresizingMaskIntoConstraints = false
+ textField.adjustsFontForContentSizeCategory = true
+ return textField
+ }()
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setupView()
+ setupConstraints()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ setupView()
+ setupConstraints()
+ }
+
+ private func setupView() {
+ addSubview(titleLabel)
+ addSubview(textField)
+ }
+
+ func configure(configuration: TextFieldConfiguration) {
+ self.layer.cornerRadius = configuration.cornerRadius
+ self.layer.borderWidth = configuration.borderWidth
+ self.layer.borderColor = configuration.borderColor.cgColor
+ self.backgroundColor = configuration.backgroundColor
+ self.textField.textColor = configuration.textColor
+ self.textField.attributedPlaceholder = NSAttributedString(string: "",
+ attributes: [.foregroundColor: configuration.placeholderForegroundColor])
+ self.titleLabel.textColor = configuration.placeholderForegroundColor
+ }
+
+ func customConfigure(labelTitle: String) {
+ titleLabel.text = labelTitle
+ titleLabel.accessibilityValue = labelTitle
+ }
+
+ private func setupConstraints() {
+ NSLayoutConstraint.activate([
+ titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: Constants.topBottomPadding),
+ titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Constants.leftRightPadding),
+ titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -Constants.leftRightPadding),
+
+ textField.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: Constants.textFieldTopPadding),
+ textField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Constants.leftRightPadding),
+ textField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Constants.leftRightPadding),
+ textField.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constants.topBottomPadding)
+ ])
+ }
+}
+
+private extension TextFieldWithLabelView {
+ enum Constants {
+ static let leftRightPadding: CGFloat = 12
+ static let topBottomPadding: CGFloat = 8
+ static let textFieldTopPadding: CGFloat = 0
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Tracking/GiniHealthTrackingDelegate.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Tracking/GiniHealthTrackingDelegate.swift
index 4d150839b..a0c6cf503 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Tracking/GiniHealthTrackingDelegate.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Tracking/GiniHealthTrackingDelegate.swift
@@ -2,7 +2,7 @@
// GiniHealthTrackingDelegate.swift
//
//
-// Created by Nadya Karaban on 05.01.22.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import Foundation
@@ -22,14 +22,12 @@ public protocol GiniHealthTrackingDelegate:
Event types relating to the payment review screen.
*/
public enum PaymentReviewScreenEventType: String {
- /// User tapped "next" button and ready to be redirected to the banking app
- case onNextButtonClicked
+ /// User tapped "To the banking app" button and ready to be redirected to the banking app
+ case onToTheBankButtonClicked
/// User tapped "close" button and closed the screen
case onCloseButtonClicked
/// User tapped "close" button and keyboard will be hidden
case onCloseKeyboardButtonClicked
- /// User tapped on the bankSelection button
- case onBankSelectionButtonClicked
}
/**
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Tracking/TrackingEvent.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Tracking/TrackingEvent.swift
index e0ef9c364..28a0d82df 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Tracking/TrackingEvent.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/Tracking/TrackingEvent.swift
@@ -2,7 +2,7 @@
// GiniHealthTrackingDelegate.swift
//
//
-// Created by Nadya Karaban on 05.01.22.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import Foundation
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/UITextField+Utils.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/UITextField+Utils.swift
deleted file mode 100644
index 2a5b9616c..000000000
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/UITextField+Utils.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// UITextField+Utils.swift
-// GiniHealth
-//
-// Created by Nadya Karaban on 18.04.21.
-//
-
-import UIKit
-public extension UITextField {
- var isReallyEmpty: Bool {
- return text?.trimmingCharacters(in: .whitespaces).isEmpty ?? true
- }
-
- func moveSelectedTextRange(from position: UITextPosition, to offset: Int) {
- if let newSelectedRangeFromTo = self.position(from: position, offset: offset),
- let newSelectedRange = self.textRange(from: newSelectedRangeFromTo, to: newSelectedRangeFromTo) {
- self.selectedTextRange = newSelectedRange
- }
- }
-}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/ZoomedImageView.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/ZoomedImageView.swift
index 75b2ff2b2..63d245e89 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/ZoomedImageView.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Core/ZoomedImageView.swift
@@ -2,7 +2,7 @@
// ZoomedImageView.swift
// GiniHealth
//
-// Created by Nadya Karaban on 08.04.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/GiniHealthSDKVersion.swift b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/GiniHealthSDKVersion.swift
index e4cd3930c..7cf9a70ca 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/GiniHealthSDKVersion.swift
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/GiniHealthSDKVersion.swift
@@ -2,7 +2,7 @@
// GiniHealthSDKVersion.swift
//
//
-// Created by Nadya Karaban on 15.10.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
-public let GiniHealthSDKVersion = "3.0.1"
+public let GiniHealthSDKVersion = "4.0.0"
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/BankSelectionTableViewCell.xib b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/BankSelectionTableViewCell.xib
new file mode 100644
index 000000000..0cc31f8b8
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/BankSelectionTableViewCell.xib
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent01.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent01.colorset/Contents.json
new file mode 100644
index 000000000..aada3ccbe
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent01.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xDC",
+ "green" : "0x9E",
+ "red" : "0x00"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent02.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent02.colorset/Contents.json
new file mode 100644
index 000000000..637e999b1
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent02.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xDF",
+ "green" : "0xB4",
+ "red" : "0x45"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent03.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent03.colorset/Contents.json
new file mode 100644
index 000000000..b8489603d
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent03.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xE1",
+ "green" : "0xC8",
+ "red" : "0x89"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent04.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent04.colorset/Contents.json
new file mode 100644
index 000000000..2263eb08b
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent04.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xF6",
+ "green" : "0xE7",
+ "red" : "0xBF"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent05.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent05.colorset/Contents.json
new file mode 100644
index 000000000..011106b40
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Accent05.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xFB",
+ "green" : "0xF5",
+ "red" : "0xE5"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark01.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark01.colorset/Contents.json
new file mode 100644
index 000000000..4f1f86771
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark01.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x30",
+ "green" : "0x1D",
+ "red" : "0x19"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark02.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark02.colorset/Contents.json
new file mode 100644
index 000000000..54f7f96a9
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark02.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x56",
+ "green" : "0x56",
+ "red" : "0x56"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark03.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark03.colorset/Contents.json
new file mode 100644
index 000000000..e8140c0a1
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark03.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x80",
+ "green" : "0x80",
+ "red" : "0x80"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark04.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark04.colorset/Contents.json
new file mode 100644
index 000000000..9a8b2f2d4
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark04.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x8E",
+ "green" : "0x8A",
+ "red" : "0x8A"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark05.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark05.colorset/Contents.json
new file mode 100644
index 000000000..604e09745
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark05.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xEB",
+ "green" : "0xE9",
+ "red" : "0xE8"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark06.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark06.colorset/Contents.json
new file mode 100644
index 000000000..a7320b817
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark06.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xF3",
+ "green" : "0xF3",
+ "red" : "0xF3"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark07.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark07.colorset/Contents.json
new file mode 100644
index 000000000..93ee5b80f
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Dark07.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xFA",
+ "green" : "0xFA",
+ "red" : "0xFA"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Feedback01.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Feedback01.colorset/Contents.json
new file mode 100644
index 000000000..cc1c43c63
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Feedback01.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x1C",
+ "green" : "0x1C",
+ "red" : "0xFA"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Feedback02.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Feedback02.colorset/Contents.json
new file mode 100644
index 000000000..96c371444
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Feedback02.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x97",
+ "green" : "0x95",
+ "red" : "0xF1"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Feedback03.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Feedback03.colorset/Contents.json
new file mode 100644
index 000000000..9dd6e8f8e
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Feedback03.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xD0",
+ "green" : "0xD0",
+ "red" : "0xEC"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Feedback04.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Feedback04.colorset/Contents.json
new file mode 100644
index 000000000..886b9e2db
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Feedback04.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xDE",
+ "green" : "0xDE",
+ "red" : "0xEE"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light01.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light01.colorset/Contents.json
new file mode 100644
index 000000000..03cea3057
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light01.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xF2",
+ "green" : "0xF2",
+ "red" : "0xF2"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light02.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light02.colorset/Contents.json
new file mode 100644
index 000000000..604e09745
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light02.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xEB",
+ "green" : "0xE9",
+ "red" : "0xE8"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light03.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light03.colorset/Contents.json
new file mode 100644
index 000000000..a7320b817
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light03.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0xF3",
+ "green" : "0xF3",
+ "red" : "0xF3"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light04.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light04.colorset/Contents.json
new file mode 100644
index 000000000..9a8b2f2d4
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light04.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x8E",
+ "green" : "0x8A",
+ "red" : "0x8A"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light05.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light05.colorset/Contents.json
new file mode 100644
index 000000000..54f7f96a9
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light05.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x56",
+ "green" : "0x56",
+ "red" : "0x56"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light06.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light06.colorset/Contents.json
new file mode 100644
index 000000000..6649025e6
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light06.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x35",
+ "green" : "0x35",
+ "red" : "0x35"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light07.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light07.colorset/Contents.json
new file mode 100644
index 000000000..5c9b7b975
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Light07.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0x21",
+ "green" : "0x21",
+ "red" : "0x21"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Success01.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Success01.colorset/Contents.json
new file mode 100644
index 000000000..6506bfa4c
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Success01.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.280",
+ "green" : "0.701",
+ "red" : "0.690"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Success02.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Success02.colorset/Contents.json
new file mode 100644
index 000000000..273ebda00
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Success02.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.627",
+ "green" : "0.841",
+ "red" : "0.836"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Success03.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Success03.colorset/Contents.json
new file mode 100644
index 000000000..e390ae423
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Success03.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.803",
+ "green" : "0.911",
+ "red" : "0.905"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Success04.colorset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Success04.colorset/Contents.json
new file mode 100644
index 000000000..26df675da
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniColors.xcassets/Success04.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.905",
+ "green" : "0.949",
+ "red" : "0.949"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/Contents.json
new file mode 100644
index 000000000..97bd95f8a
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/Contents.json
@@ -0,0 +1,57 @@
+{
+ "images" : [
+ {
+ "filename" : "img_button_app_store_english.pdf",
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "filename" : "Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917 1.pdf",
+ "idiom" : "universal"
+ },
+ {
+ "filename" : "img_button_app_store_german.pdf",
+ "idiom" : "universal",
+ "locale" : "de"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "filename" : "Download_on_the_App_Store_Badge_DE_RGB_wht_092917 1.pdf",
+ "idiom" : "universal",
+ "locale" : "de"
+ },
+ {
+ "filename" : "img_button_app_store_english 1.pdf",
+ "idiom" : "universal",
+ "locale" : "en"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "filename" : "Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917.pdf",
+ "idiom" : "universal",
+ "locale" : "en"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "localizable" : true
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/Download_on_the_App_Store_Badge_DE_RGB_wht_092917 1.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/Download_on_the_App_Store_Badge_DE_RGB_wht_092917 1.pdf
new file mode 100644
index 000000000..e3e53718b
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/Download_on_the_App_Store_Badge_DE_RGB_wht_092917 1.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917 1.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917 1.pdf
new file mode 100644
index 000000000..9da698933
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917 1.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917.pdf
new file mode 100644
index 000000000..9da698933
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/img_button_app_store_english 1.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/img_button_app_store_english 1.pdf
new file mode 100644
index 000000000..11d7fdacf
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/img_button_app_store_english 1.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/img_button_app_store_english.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/img_button_app_store_english.pdf
new file mode 100644
index 000000000..11d7fdacf
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/img_button_app_store_english.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/img_button_app_store_german.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/img_button_app_store_german.pdf
new file mode 100644
index 000000000..ae8641d24
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/appStoreIcon.imageset/img_button_app_store_german.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/giniLogo.imageset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/giniLogo.imageset/Contents.json
new file mode 100644
index 000000000..6107f98d0
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/giniLogo.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "giniLogo.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/giniLogo.imageset/giniLogo.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/giniLogo.imageset/giniLogo.pdf
new file mode 100644
index 000000000..782016988
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/giniLogo.imageset/giniLogo.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_close.imageset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_close.imageset/Contents.json
new file mode 100644
index 000000000..90e642f04
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_close.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "ic_close.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_close.imageset/ic_close.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_close.imageset/ic_close.pdf
new file mode 100644
index 000000000..5aeb568d9
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_close.imageset/ic_close.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_minus.imageset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_minus.imageset/Contents.json
new file mode 100644
index 000000000..adf2ef78b
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_minus.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "ic_minus.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_minus.imageset/ic_minus.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_minus.imageset/ic_minus.pdf
new file mode 100644
index 000000000..621faf32c
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_minus.imageset/ic_minus.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_plus.imageset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_plus.imageset/Contents.json
new file mode 100644
index 000000000..b5295c520
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_plus.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "ic_plus.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_plus.imageset/ic_plus.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_plus.imageset/ic_plus.pdf
new file mode 100644
index 000000000..92aa238a5
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/ic_plus.imageset/ic_plus.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/iconChevronDown.imageset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/iconChevronDown.imageset/Contents.json
new file mode 100644
index 000000000..2f603a012
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/iconChevronDown.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "ic_chevron-down.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/iconChevronDown.imageset/ic_chevron-down.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/iconChevronDown.imageset/ic_chevron-down.pdf
new file mode 100644
index 000000000..0f4e4e39a
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/iconChevronDown.imageset/ic_chevron-down.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/info.circle.imageset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/info.circle.imageset/Contents.json
new file mode 100644
index 000000000..d6bbea069
--- /dev/null
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/info.circle.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Group 188.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/info.circle.imageset/Group 188.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/info.circle.imageset/Group 188.pdf
new file mode 100644
index 000000000..e07092dd0
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/info.circle.imageset/Group 188.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/selectionIndicator.imageset/Contents.json b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/selectionIndicator.imageset/Contents.json
index 7f31cd7eb..be085e58d 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/selectionIndicator.imageset/Contents.json
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/selectionIndicator.imageset/Contents.json
@@ -1,7 +1,7 @@
{
"images" : [
{
- "filename" : "selectionIndicator.png",
+ "filename" : "ic_check_selected.pdf",
"idiom" : "universal"
}
],
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/selectionIndicator.imageset/ic_check_selected.pdf b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/selectionIndicator.imageset/ic_check_selected.pdf
new file mode 100644
index 000000000..0f146115f
Binary files /dev/null and b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/selectionIndicator.imageset/ic_check_selected.pdf differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/selectionIndicator.imageset/selectionIndicator.png b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/selectionIndicator.imageset/selectionIndicator.png
deleted file mode 100644
index ffb303e0a..000000000
Binary files a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/GiniImages.xcassets/selectionIndicator.imageset/selectionIndicator.png and /dev/null differ
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/PaymentReview.storyboard b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/PaymentReview.storyboard
index 12baa4334..fbd0f4f19 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/PaymentReview.storyboard
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/PaymentReview.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -23,7 +23,7 @@
-
+
@@ -31,13 +31,13 @@
-
+
-
+
@@ -46,7 +46,7 @@
-
+
@@ -68,10 +68,10 @@
-
+
-
+
@@ -80,34 +80,28 @@
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
-
-
-
-
-
-
+
-
+
@@ -118,42 +112,27 @@
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
+
-
+
@@ -189,23 +168,17 @@
-
+
-
-
-
+
+
+
-
+
-
-
-
-
-
-
-
+
-
+
@@ -217,60 +190,28 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -286,7 +227,6 @@
-
@@ -317,41 +257,39 @@
+
-
-
-
-
-
+
+
-
+
-
+
-
+
-
-
-
+
-
-
+
+
+
+
@@ -360,7 +298,6 @@
-
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/de.lproj/Localizable.strings b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/de.lproj/Localizable.strings
index 70f667580..4b864f3d2 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/de.lproj/Localizable.strings
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/de.lproj/Localizable.strings
@@ -2,25 +2,50 @@
File.strings
Pods
- Created by Nadya Karaban on 08.04.21.
-
+ // Copyright © 2024 Gini GmbH. All rights reserved.
+
*/
"ginihealth.reviewscreen.recipient.placeholder" = "Empfänger";
"ginihealth.reviewscreen.iban.placeholder" = "IBAN";
-"ginihealth.reviewscreen.amount.placeholder" = "0,00 €";
+"ginihealth.reviewscreen.amount.placeholder" = "Betrag";
"ginihealth.reviewscreen.usage.placeholder" = "Verwendungszweck";
-"ginihealth.reviewscreen.infobar.message" = "Bitte prüfe die vorausgefüllten Daten";
-"ginihealth.reviewscreen.next.button.title" = "Weiter";
-"ginihealth.errors.no.banking.app.installed" = "Leider unterstützt deine Banking App diese Funktion noch nicht. Probier doch mal die ING App.";
+"ginihealth.reviewscreen.infobar.message" = "Bitte prüfen Sie die vorausgefüllten Daten.";
+"ginihealth.reviewscreen.banking.app.button.label" = "Zur Banking App";
"ginihealth.errors.default" = "Oh da ist was schief gelaufen. Probiere es noch einmal.";
"ginihealth.errors.failed.payment.request.creation" = "Oh da ist was schief gelaufen. Probiere es noch einmal.";
"ginihealth.errors.failed.recipient.non.empty.check" = "Empfänger ist notwendig.";
"ginihealth.errors.failed.iban.non.empty.check" = "IBAN ist notwendig.";
-"ginihealth.errors.failed.iban.validation.check" = "IBAN ist ungültig";
-"ginihealth.errors.failed.amount.non.empty.check" = "Summe kann nicht 0 sein";
+"ginihealth.errors.failed.iban.validation.check" = "IBAN ist ungültig.";
+"ginihealth.errors.failed.amount.non.empty.check" = "Ungültig.";
"ginihealth.errors.failed.purpose.non.empty.check" = "Verwendungszweck ist notwendig.";
-"ginihealth.errors.failed.default.textfield.validation.check" = "Das Feld ist ungültig";
+"ginihealth.errors.failed.default.textfield.validation.check" = "Das Feld ist ungültig.";
"ginihealth.alert.ok.title" = "OK";
-"ginihealth.bankprovidersscreen.title" = "Banking-App auswählen";
+"ginihealth.paymentcomponent.moreInformation.label" = "Bezahldaten an Banking-App übergeben und dort direkt bezahlen. Mehr Informationen.";
+"ginihealth.paymentcomponent.moreInformation.underlined.part" = "Mehr Informationen.";
+"ginihealth.paymentcomponent.selectYourBank.label" = "Wählen Sie Ihre Bank aus";
+"ginihealth.paymentcomponent.payInvoice.label" = "Rechnung bezahlen";
+"ginihealth.paymentcomponent.poweredByGini.label" = "Powered by";
+"ginihealth.paymentcomponent.selectBank.label" = "Bank auswählen";
+"ginihealth.paymentcomponent.paymentproviderslist.description" = "Sie können die Rechnung nur bezahlen, wenn Sie ein Konto bei einer der unten aufgeführten Banken haben.";
+"ginihealth.paymentcomponent.paymentinfo.title.label" = "Mehr Informationen";
+"ginihealth.paymentcomponent.paymentinfo.payBills.title.label" = "Rechnungen ganz einfach mit der Banking-App bezahlen.";
+"ginihealth.paymentcomponent.paymentinfo.payBills.description.label" = "Arztrechnungen und andere eingereichte Belege können jetzt ganz einfach bezahlt werden.\nDie Bezahldaten wie IBAN, Betrag, Empfänger und Verwendungszweck werden nahtlos in die Banking-App übergeben und dort nur noch bestätigt.\nSie können die Rechnung auch parken und innerhalb von 3 Monaten nach Upload begleichen.\nDie für die Zahlung notwendigen Daten werden verschlüsselt und sicher an Ihre Banking-App übertragen. Es gelten die Datenschutzbestimmungen Ihrer Bank.\nUnterstützt von den größten Bankinstituten. Integration durch Gini[LINK].";
+"ginihealth.paymentcomponent.paymentinfo.payBills.description.clickable.text" = "Gini[LINK]";
+"ginihealth.paymentcomponent.paymentinfo.questions.title.label" = "Häufig gestellte Fragen";
+"ginihealth.paymentcomponent.paymentinfo.questions.question.1" = "Kann ich Rechnungen einreichen und später bezahlen?";
+"ginihealth.paymentcomponent.paymentinfo.questions.question.2" = "Ist der Service kostenlos?";
+"ginihealth.paymentcomponent.paymentinfo.questions.question.3" = "Sind meine Daten sicher?";
+"ginihealth.paymentcomponent.paymentinfo.questions.question.4" = "Wer oder Was ist Gini?";
+"ginihealth.paymentcomponent.paymentinfo.questions.question.5" = "Welches Format muss die eingereichte Rechnung haben?";
+"ginihealth.paymentcomponent.paymentinfo.questions.question.6" = "Wie erkenne ich, welche Banken unterstützt werden?";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.1" = "Ja, das ist möglich. So können insbesondere größere Beträge erst nach der erfolgten Erstattung bezahlt werden.";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.2" = "Ja, die Übertragung der Bezahldaten in die gewählte Banking-App ist kostenlos. Für die eigentliche Überweisung können je nach Kontenmodell Gebühren anfallen – wenden Sie sich bitte für weitere Details an Ihre Bank.";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.3" = "Ja! Die Übertragung der Daten an die Banking-App erfolgt verschlüsselt über einen Server von Gini. Gini nimmt die Zahlungsdaten entgegen und leitet sie in die Banking-App weiter. Dort besteht stets die Möglichkeit, die Zahlungsdaten zu überprüfen, bevor die Überweisung ausgeführt wird. Gini hat hierfür sowohl mit der Versicherung als auch mit den Banken Verträge geschlossen, und wir lassen uns regelmäßig von beiden auditieren.";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.4" = "Gini macht das Bezahlen automagisch einfach. Das Münchner Unternehmen, das hinter der Fotoüberweisung steht, arbeitet mit den führenden deutschen Banken und Versicherungen zusammen, um die direkte Zahlung mit der Hausbank zu ermöglichen.\nGini ist für maximale Datensicherheit ISO 27001 zertifiziert und betreibt eigene Rechner in einem ISO 27001 zertifizierten Rechenzentrum in Deutschland. Weitere Informationen finden Sie in der Datenschutzerklärung[LINK] sowie auf der Website von Gini[LINK].";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.5" = "Ob Foto einer Rechnung, Screenshot, oder digitales PDF – jedes Format ist geeignet. Bitte achten Sie lediglich darauf, dass alle Bezahlinformationen, wie IBAN, Empfänger, Verwendungszweck und Betrag sichtbar und nicht abgeschnitten sind.";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.6" = "Im Bank-Auswahlmenü werden die Banken angezeigt, die die Gini-Bezahlfunktion unterstützen. Voraussetzung für die Nutzung ist, dass Sie die mobile Banking-App ihrer Bank auf demselben Smartphone oder Tablet installiert haben, auf dem Sie die Versicherungs-App nutzen. Sollten Sie keine der Banking-Apps installiert haben, können Sie diese aus dem Apple App- oder Google Playstore herunterladen. Eine anschließende Aktivierung der Banking-App kann nötig sein.";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.clickable.text" = "Datenschutzerklärung[LINK]";
+"ginihealth.paymentcomponent.paymentinfo.gini.link" = "https://gini.net/";
+"ginihealth.paymentcomponent.paymentinfo.gini.privacypolicy.link" = "";
diff --git a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/en.lproj/Localizable.strings b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/en.lproj/Localizable.strings
index bfc807fa6..0cbb0aaf9 100644
--- a/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/en.lproj/Localizable.strings
+++ b/HealthSDK/GiniHealthSDK/Sources/GiniHealthSDK/Resources/en.lproj/Localizable.strings
@@ -2,25 +2,50 @@
File.strings
Pods
- Created by Nadya Karaban on 08.04.21.
-
+ // Copyright © 2024 Gini GmbH. All rights reserved.
+
*/
"ginihealth.reviewscreen.recipient.placeholder" = "Recipient";
"ginihealth.reviewscreen.iban.placeholder" = "IBAN";
-"ginihealth.reviewscreen.amount.placeholder" = "0,00 €";
-"ginihealth.reviewscreen.usage.placeholder" = "Purpose";
-"ginihealth.reviewscreen.next.button.title" = "Next";
-"ginihealth.errors.no.banking.app.installed" = "Your banking app does not support this service. Have you tried ING app yet?";
-"ginihealth.reviewscreen.infobar.message" = "Please review the extracted information below";
+"ginihealth.reviewscreen.amount.placeholder" = "Amount";
+"ginihealth.reviewscreen.usage.placeholder" = "Reference number";
+"ginihealth.reviewscreen.banking.app.button.label" = "To the banking app";
+"ginihealth.reviewscreen.infobar.message" = "Please check the pre-filled data.";
"ginihealth.errors.default" = "Oops something went wrong. Please try again.";
"ginihealth.errors.failed.payment.request.creation" = "Oops something went wrong. Please try again.";
"ginihealth.errors.failed.recipient.non.empty.check" = "Recipient is required.";
"ginihealth.errors.failed.iban.non.empty.check" = "IBAN is required.";
-"ginihealth.errors.failed.iban.validation.check" = "IBAN is not valid";
-"ginihealth.errors.failed.amount.non.empty.check" = "Amount can't be 0";
+"ginihealth.errors.failed.iban.validation.check" = "IBAN is not valid.";
+"ginihealth.errors.failed.amount.non.empty.check" = "Invalid.";
"ginihealth.errors.failed.purpose.non.empty.check" = "Purpose is required.";
-"ginihealth.errors.failed.default.textfield.validation.check" = "The field is not valid";
+"ginihealth.errors.failed.default.textfield.validation.check" = "The field is not valid.";
"ginihealth.alert.ok.title" = "OK";
-"ginihealth.bankprovidersscreen.title" = "Select Banking-App";
+"ginihealth.paymentcomponent.moreInformation.label" = "Transfer payment data to the banking app and pay there directly. More information.";
+"ginihealth.paymentcomponent.moreInformation.underlined.part" = "More information.";
+"ginihealth.paymentcomponent.selectYourBank.label" = "Select your bank";
+"ginihealth.paymentcomponent.payInvoice.label" = "Pay the invoice";
+"ginihealth.paymentcomponent.poweredByGini.label" = "Powered by";
+"ginihealth.paymentcomponent.selectBank.label" = "Select bank";
+"ginihealth.paymentcomponent.paymentproviderslist.description" = "You can only pay the bill if you have an account with one of the banks listed below.";
+"ginihealth.paymentcomponent.paymentinfo.title.label" = "More information";
+"ginihealth.paymentcomponent.paymentinfo.payBills.title.label" = "Pay bills easily with the banking app.";
+"ginihealth.paymentcomponent.paymentinfo.payBills.description.label" = "Medical bills and other submitted receipts can now be paid very easily.\nThe payment data such as IBAN, amount, recipient and purpose are seamlessly transferred to the banking app, and the payment only needs to be confirmed there.\nYou can also park the invoice and pay it within 3 months after uploading.\nThe data is encrypted and transferred securely to your banking app. The data protection regulations of your bank apply.\nSupported by the largest banks. Integration by Gini[LINK].";
+"ginihealth.paymentcomponent.paymentinfo.payBills.description.clickable.text" = "Gini[LINK]";
+"ginihealth.paymentcomponent.paymentinfo.questions.title.label" = "Frequently asked questions";
+"ginihealth.paymentcomponent.paymentinfo.questions.question.1" = "Can I submit invoices and pay them later?";
+"ginihealth.paymentcomponent.paymentinfo.questions.question.2" = "Is the service free of charge?";
+"ginihealth.paymentcomponent.paymentinfo.questions.question.3" = "Is my data secure?";
+"ginihealth.paymentcomponent.paymentinfo.questions.question.4" = "Who or what is Gini?";
+"ginihealth.paymentcomponent.paymentinfo.questions.question.5" = "What format must the submitted invoice have?";
+"ginihealth.paymentcomponent.paymentinfo.questions.question.6" = "How do I know which banks are supported?";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.1" = "Yes, this is possible. Larger amounts in particular can be paid after the reimbursement has been made.";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.2" = "Yes, transferring the payment data to the selected banking app is free of charge. Charges may apply for the actual transfer, depending on the account model - please contact your bank for further details.";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.3" = "Yes, the data is transferred to the banking app in encrypted form via a Gini server. Gini receives the payment data and forwards it to the banking app. There you always have the option of checking the payment data before the transfer is executed. Gini has concluded contracts with the insurance company and the banks for this purpose, and both regularly audit us.";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.4" = "Gini makes simplifies payments. The Munich-based company behind the photo payment works with the leading German banks and insurance companies to enable direct payment with your bank.\nGini is ISO 27001 certified for maximum data security and operates its own server machines in an ISO 27001 certified data center in Germany. Further information can be found in the privacy policy[LINK] and on the Gini[LINK] website.";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.5" = "Whether a photo of an invoice, screenshot or digital PDF - any format is suitable. Please just make sure that all payment information such as IBAN, recipient, purpose and amount are visible and not cut off.";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.6" = "The banks that support the Gini payment function are displayed in the bank selection menu. To use it, you must have installed your bank's mobile banking app on the same smartphone or tablet on which you use the insurance app. If you have not installed any of the banking apps, you can download them from the Apple App Store or Google Playstore. Subsequent activation of the banking app may be necessary.";
+"ginihealth.paymentcomponent.paymentinfo.questions.answer.clickable.text" = "privacy policy[LINK]";
+"ginihealth.paymentcomponent.paymentinfo.gini.link" = "https://gini.net/en/";
+"ginihealth.paymentcomponent.paymentinfo.gini.privacypolicy.link" = "";
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample.xcodeproj/project.pbxproj b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample.xcodeproj/project.pbxproj
index 586bd15f8..cdd8adda2 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample.xcodeproj/project.pbxproj
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample.xcodeproj/project.pbxproj
@@ -8,6 +8,18 @@
/* Begin PBXBuildFile section */
284A53502A86493100183EA7 /* SelectAPIViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 284A534F2A86493100183EA7 /* SelectAPIViewController.xib */; };
+ 7DB661832B554622004537AA /* InvoicesListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB661822B554622004537AA /* InvoicesListCoordinator.swift */; };
+ 7DB661862B554A6F004537AA /* InvoicesListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB661842B554A6F004537AA /* InvoicesListViewController.swift */; };
+ 7DB6618B2B554ADF004537AA /* InvoiceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB661892B554ADF004537AA /* InvoiceTableViewCell.swift */; };
+ 7DB6618C2B554ADF004537AA /* InvoiceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7DB6618A2B554ADF004537AA /* InvoiceTableViewCell.xib */; };
+ 7DB6618E2B5687B4004537AA /* HardcodedInvoices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB6618D2B5687B4004537AA /* HardcodedInvoices.swift */; };
+ 7DB661952B5687F7004537AA /* health-invoice-2.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 7DB661902B5687F7004537AA /* health-invoice-2.jpg */; };
+ 7DB661962B5687F7004537AA /* health-invoice-3.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 7DB661912B5687F7004537AA /* health-invoice-3.jpg */; };
+ 7DB661972B5687F7004537AA /* health-invoice-4.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 7DB661922B5687F7004537AA /* health-invoice-4.jpg */; };
+ 7DB661982B5687F7004537AA /* health-invoice-1.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 7DB661932B5687F7004537AA /* health-invoice-1.jpg */; };
+ 7DB661992B5687F7004537AA /* health-invoice-5.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 7DB661942B5687F7004537AA /* health-invoice-5.jpg */; };
+ 7DB6619B2B56AE80004537AA /* InvoicesListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB6619A2B56AE80004537AA /* InvoicesListViewModel.swift */; };
+ 7DB6619D2B56C0B7004537AA /* InvoiceTableViewCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB6619C2B56C0B7004537AA /* InvoiceTableViewCellModel.swift */; };
F4206BBC2A1B9C6900E5E101 /* GiniCaptureSDK in Frameworks */ = {isa = PBXBuildFile; productRef = F4206BBB2A1B9C6900E5E101 /* GiniCaptureSDK */; };
F4206BBE2A1BB7CC00E5E101 /* ScreenAPICoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4206BBD2A1BB7CC00E5E101 /* ScreenAPICoordinator.swift */; };
F4206BC02A1BB86B00E5E101 /* RootNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4206BBF2A1BB86B00E5E101 /* RootNavigationController.swift */; };
@@ -61,6 +73,18 @@
/* Begin PBXFileReference section */
284A534F2A86493100183EA7 /* SelectAPIViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SelectAPIViewController.xib; sourceTree = ""; };
+ 7DB661822B554622004537AA /* InvoicesListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoicesListCoordinator.swift; sourceTree = ""; };
+ 7DB661842B554A6F004537AA /* InvoicesListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoicesListViewController.swift; sourceTree = ""; };
+ 7DB661892B554ADF004537AA /* InvoiceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoiceTableViewCell.swift; sourceTree = ""; };
+ 7DB6618A2B554ADF004537AA /* InvoiceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InvoiceTableViewCell.xib; sourceTree = ""; };
+ 7DB6618D2B5687B4004537AA /* HardcodedInvoices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardcodedInvoices.swift; sourceTree = ""; };
+ 7DB661902B5687F7004537AA /* health-invoice-2.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "health-invoice-2.jpg"; sourceTree = ""; };
+ 7DB661912B5687F7004537AA /* health-invoice-3.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "health-invoice-3.jpg"; sourceTree = ""; };
+ 7DB661922B5687F7004537AA /* health-invoice-4.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "health-invoice-4.jpg"; sourceTree = ""; };
+ 7DB661932B5687F7004537AA /* health-invoice-1.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "health-invoice-1.jpg"; sourceTree = ""; };
+ 7DB661942B5687F7004537AA /* health-invoice-5.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "health-invoice-5.jpg"; sourceTree = ""; };
+ 7DB6619A2B56AE80004537AA /* InvoicesListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoicesListViewModel.swift; sourceTree = ""; };
+ 7DB6619C2B56C0B7004537AA /* InvoiceTableViewCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvoiceTableViewCellModel.swift; sourceTree = ""; };
F4206BBD2A1BB7CC00E5E101 /* ScreenAPICoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenAPICoordinator.swift; sourceTree = ""; };
F4206BBF2A1BB86B00E5E101 /* RootNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootNavigationController.swift; sourceTree = ""; };
F4206BC12A1D012E00E5E101 /* HealthNetworkingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthNetworkingService.swift; sourceTree = ""; };
@@ -140,6 +164,33 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 7DB661882B554A79004537AA /* InvoicesList */ = {
+ isa = PBXGroup;
+ children = (
+ 7DB661822B554622004537AA /* InvoicesListCoordinator.swift */,
+ 7DB661842B554A6F004537AA /* InvoicesListViewController.swift */,
+ 7DB6619A2B56AE80004537AA /* InvoicesListViewModel.swift */,
+ 7DB661892B554ADF004537AA /* InvoiceTableViewCell.swift */,
+ 7DB6619C2B56C0B7004537AA /* InvoiceTableViewCellModel.swift */,
+ 7DB6618A2B554ADF004537AA /* InvoiceTableViewCell.xib */,
+ 7DB6618F2B5687EA004537AA /* HardcodedInvoices */,
+ );
+ path = InvoicesList;
+ sourceTree = "";
+ };
+ 7DB6618F2B5687EA004537AA /* HardcodedInvoices */ = {
+ isa = PBXGroup;
+ children = (
+ 7DB6618D2B5687B4004537AA /* HardcodedInvoices.swift */,
+ 7DB661932B5687F7004537AA /* health-invoice-1.jpg */,
+ 7DB661902B5687F7004537AA /* health-invoice-2.jpg */,
+ 7DB661912B5687F7004537AA /* health-invoice-3.jpg */,
+ 7DB661922B5687F7004537AA /* health-invoice-4.jpg */,
+ 7DB661942B5687F7004537AA /* health-invoice-5.jpg */,
+ );
+ path = HardcodedInvoices;
+ sourceTree = "";
+ };
F4C36A7F270C6EBF00CCC69C = {
isa = PBXGroup;
children = (
@@ -221,6 +272,7 @@
F4EC3904273C2621007045DC /* PartialDocument.swift */,
F4EC3907273C2621007045DC /* SelectAPIViewController.swift */,
284A534F2A86493100183EA7 /* SelectAPIViewController.xib */,
+ 7DB661882B554A79004537AA /* InvoicesList */,
F4EC3908273C2622007045DC /* UIViewController.swift */,
F4EC38F4273C2311007045DC /* Base.lproj */,
F4EC38F3273C2311007045DC /* Credentials.plist */,
@@ -394,11 +446,17 @@
buildActionMask = 2147483647;
files = (
F4EC38CE273C21E5007045DC /* LaunchScreen.storyboard in Resources */,
+ 7DB6618C2B554ADF004537AA /* InvoiceTableViewCell.xib in Resources */,
+ 7DB661952B5687F7004537AA /* health-invoice-2.jpg in Resources */,
F464B17627CD2147001735B9 /* Localizable.strings in Resources */,
F4EC38FA273C2311007045DC /* Base.lproj in Resources */,
+ 7DB661992B5687F7004537AA /* health-invoice-5.jpg in Resources */,
F4EC38F9273C2311007045DC /* Credentials.plist in Resources */,
284A53502A86493100183EA7 /* SelectAPIViewController.xib in Resources */,
F4EC3913273C2B41007045DC /* Assets.xcassets in Resources */,
+ 7DB661982B5687F7004537AA /* health-invoice-1.jpg in Resources */,
+ 7DB661972B5687F7004537AA /* health-invoice-4.jpg in Resources */,
+ 7DB661962B5687F7004537AA /* health-invoice-3.jpg in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -435,19 +493,25 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 7DB661832B554622004537AA /* InvoicesListCoordinator.swift in Sources */,
F4EC390B273C2622007045DC /* SelectAPIViewController.swift in Sources */,
F4206BC22A1D012E00E5E101 /* HealthNetworkingService.swift in Sources */,
F4EC390D273C2622007045DC /* Coordinator.swift in Sources */,
F4EC38F8273C2311007045DC /* CredentialsManager.swift in Sources */,
+ 7DB6618B2B554ADF004537AA /* InvoiceTableViewCell.swift in Sources */,
F4EC38F5273C2311007045DC /* UIViewController+Utils.swift in Sources */,
F4EC38C6273C21E4007045DC /* ViewController.swift in Sources */,
+ 7DB6618E2B5687B4004537AA /* HardcodedInvoices.swift in Sources */,
F4EC3910273C2622007045DC /* UIViewController.swift in Sources */,
+ 7DB6619B2B56AE80004537AA /* InvoicesListViewModel.swift in Sources */,
F4206BC02A1BB86B00E5E101 /* RootNavigationController.swift in Sources */,
F4EC38C2273C21E4007045DC /* AppDelegate.swift in Sources */,
+ 7DB661862B554A6F004537AA /* InvoicesListViewController.swift in Sources */,
F4EC38C4273C21E4007045DC /* SceneDelegate.swift in Sources */,
F4206BBE2A1BB7CC00E5E101 /* ScreenAPICoordinator.swift in Sources */,
F4EC390A273C2622007045DC /* AppCoordinator.swift in Sources */,
F4EC390E273C2622007045DC /* PartialDocument.swift in Sources */,
+ 7DB6619D2B56C0B7004537AA /* InvoiceTableViewCellModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample.xcodeproj/xcshareddata/xcschemes/GiniHealthSDKExample.xcscheme b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample.xcodeproj/xcshareddata/xcschemes/GiniHealthSDKExample.xcscheme
new file mode 100644
index 000000000..3dc2ff4fe
--- /dev/null
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample.xcodeproj/xcshareddata/xcschemes/GiniHealthSDKExample.xcscheme
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppCoordinator.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppCoordinator.swift
index 207b3b876..1186c076f 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppCoordinator.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppCoordinator.swift
@@ -6,7 +6,6 @@
// Copyright © 2017 Gini GmbH. All rights reserved.
//
-import Foundation
import UIKit
import GiniCaptureSDK
import GiniHealthAPILibrary
@@ -48,8 +47,9 @@ final class AppCoordinator: Coordinator {
}()
private lazy var client: GiniHealthAPILibrary.Client = CredentialsManager.fetchClientFromBundle()
- private lazy var apiLib = GiniHealthAPI.Builder(client: client).build()
+ private lazy var apiLib = GiniHealthAPI.Builder(client: client, logLevel: .debug).build()
private lazy var health = GiniHealth(with: apiLib)
+ private lazy var paymentComponentsController = PaymentComponentsController(giniHealth: health)
private var documentMetadata: GiniHealthAPILibrary.Document.Metadata?
private let documentMetadataBranchId = "GiniHealthExampleIOS"
@@ -66,6 +66,8 @@ final class AppCoordinator: Coordinator {
func start() {
self.showSelectAPIScreen()
+ paymentComponentsController.delegate = self
+ paymentComponentsController.loadPaymentProviders()
}
func processExternalDocument(withUrl url: URL, sourceApplication: String?) {
@@ -106,7 +108,6 @@ final class AppCoordinator: Coordinator {
self.window.makeKeyAndVisible()
}
-
fileprivate func showScreenAPI(with pages: [GiniCapturePage]? = nil) {
let metadata = GiniHealthAPILibrary.Document.Metadata(branchId: documentMetadataBranchId,
additionalHeaders: [documentMetadataAppFlowKey: "ScreenAPI"])
@@ -114,9 +115,11 @@ final class AppCoordinator: Coordinator {
let screenAPICoordinator = ScreenAPICoordinator(configuration: giniConfiguration,
importedDocuments: pages?.map { $0.document },
client: GiniHealthAPILibrary.Client(id: self.client.id,
- secret: self.client.secret,
- domain: self.client.domain),
- documentMetadata: metadata)
+ secret: self.client.secret,
+ domain: self.client.domain),
+ documentMetadata: metadata,
+ hardcodedInvoicesController: HardcodedInvoicesController(),
+ paymentComponentController: paymentComponentsController)
screenAPICoordinator.delegate = self
@@ -131,38 +134,8 @@ final class AppCoordinator: Coordinator {
private var testDocument: GiniHealthAPILibrary.Document?
private var testDocumentExtractions: [GiniHealthAPILibrary.Extraction]?
- fileprivate func checkIfAnyBankingAppsInstalled(from viewController: UIViewController, completion: @escaping () -> Void) {
- health.checkIfAnyPaymentProviderAvailable { result in
- switch(result) {
- case .success(_):
- completion()
- case .failure(_):
- let alertViewController = UIAlertController(title: "",
- message: "We didn't find any banking apps installed",
- preferredStyle: .alert)
-
- alertViewController.addAction(UIAlertAction(title: "OK", style: .default) { _ in
- alertViewController.dismiss(animated: true)
- })
- viewController.present(alertViewController, animated: true)
- }
- }
- }
-
fileprivate func showPaymentReviewWithTestDocument() {
let configuration = GiniHealthConfiguration()
- // Font configuration
- let regularFont = UIFont(name: "Avenir", size: 15) ?? UIFont.systemFont(ofSize: 15)
- let boldFont = UIFont(name: "Avenir Heavy", size: 14) ?? UIFont.systemFont(ofSize: 15)
- configuration.customFont = GiniFont(regular: regularFont, bold: boldFont, light: regularFont, thin: regularFont)
- // Pay button configuration
- configuration.payButtonTitleFont = boldFont
- // Uncomment to test disabled state
- //configuration.payButtonDisabledTextColor = GiniColor(lightModeColor: .yellow, darkModeColor: .yellow)
- //configuration.payButtonDisabledBackgroundColor = GiniColor(lightModeColor: .red, darkModeColor: .red)
- // Page indicator color configuration
- configuration.currentPageIndicatorTintColor = GiniColor(lightModeColor: .systemBlue, darkModeColor: .systemBlue)
- configuration.pageIndicatorTintColor = GiniColor(lightModeColor: .darkGray, darkModeColor: .darkGray)
// Show the close button to dismiss the payment review screen
configuration.showPaymentReviewCloseButton = true
@@ -170,66 +143,81 @@ final class AppCoordinator: Coordinator {
health.delegate = self
health.setConfiguration(configuration)
-
- checkIfAnyBankingAppsInstalled(from: self.rootViewController) {
- if let document = self.testDocument {
- self.selectAPIViewController.showActivityIndicator()
-
- self.health.fetchDataForReview(documentId: document.id) { result in
- switch result {
- case .success(let data):
- let vc = PaymentReviewViewController.instantiate(with: self.health, data: data, trackingDelegate: self)
- vc.modalPresentationStyle = .overCurrentContext
- vc.modalTransitionStyle = .coverVertical
- self.rootViewController.present(vc, animated: true)
- case .failure(let error):
- print("❌ Document data fetching failed: \(String(describing: error))")
- }
+
+ if let document = self.testDocument {
+ self.selectAPIViewController.showActivityIndicator()
+
+ self.health.fetchDataForReview(documentId: document.id) { result in
+ switch result {
+ case .success(let data):
+ self.health.checkIfDocumentIsPayable(docId: document.id, completion: { [weak self] resultPayable in
+ switch resultPayable {
+ case .success(let isPayable):
+ let invoice = DocumentWithExtractions(documentID: document.id,
+ extractions: data.extractions,
+ isPayable: isPayable)
+ self?.showInvoicesList(invoices: [invoice])
+ case .failure(let error):
+ print("❌ Checking if document is payable failed: \(String(describing: error))")
+ }
+ self?.selectAPIViewController.hideActivityIndicator()
+ })
+ case .failure(let error):
+ print("❌ Document data fetching failed: \(String(describing: error))")
self.selectAPIViewController.hideActivityIndicator()
}
- } else {
- // Upload the test document image
- let testDocumentImage = UIImage(named: "testDocument")!
- let testDocumentData = testDocumentImage.jpegData(compressionQuality: 1)!
-
- self.selectAPIViewController.showActivityIndicator()
-
- self.health.documentService.createDocument(fileName: nil,
- docType: nil,
- type: .partial(testDocumentData),
- metadata: nil) { result in
- switch result {
- case .success(let createdDocument):
- let partialDocInfo = GiniHealthAPILibrary.PartialDocumentInfo(document: createdDocument.links.document)
- self.health.documentService.createDocument(fileName: nil,
- docType: nil,
- type: .composite(CompositeDocumentInfo(partialDocuments: [partialDocInfo])),
- metadata: nil) { result in
- switch result {
- case .success(let compositeDocument):
- self.health.setDocumentForReview(documentId: compositeDocument.id) { result in
- switch result {
- case .success(let extractions):
- self.testDocument = compositeDocument
- self.testDocumentExtractions = extractions
-
- // Show the payment review screen
- let vc = PaymentReviewViewController.instantiate(with: self.health, document: compositeDocument, extractions: extractions, trackingDelegate: self)
- self.rootViewController.present(vc, animated: true)
- case .failure(let error):
- print("❌ Setting document for review failed: \(String(describing: error))")
- }
+ }
+ } else {
+ // Upload the test document image
+ let testDocumentImage = UIImage(named: "testDocument")!
+ let testDocumentData = testDocumentImage.jpegData(compressionQuality: 1)!
+
+ self.selectAPIViewController.showActivityIndicator()
+
+ self.health.documentService.createDocument(fileName: nil,
+ docType: nil,
+ type: .partial(testDocumentData),
+ metadata: nil) { result in
+ switch result {
+ case .success(let createdDocument):
+ let partialDocInfo = GiniHealthAPILibrary.PartialDocumentInfo(document: createdDocument.links.document)
+ self.health.documentService.createDocument(fileName: nil,
+ docType: nil,
+ type: .composite(CompositeDocumentInfo(partialDocuments: [partialDocInfo])),
+ metadata: nil) { result in
+ switch result {
+ case .success(let compositeDocument):
+ self.health.setDocumentForReview(documentId: compositeDocument.id) { result in
+ switch result {
+ case .success(let extractions):
+ self.testDocument = compositeDocument
+ self.testDocumentExtractions = extractions
+
+ self.health.checkIfDocumentIsPayable(docId: compositeDocument.id, completion: { [weak self] resultPayable in
+ switch resultPayable {
+ case .success(let isPayable):
+ let invoice = DocumentWithExtractions(documentID: compositeDocument.id,
+ extractions: extractions,
+ isPayable: isPayable)
+ self?.showInvoicesList(invoices: [invoice])
+ case .failure(let error):
+ print("❌ Checking if document is payable failed: \(String(describing: error))")
+ }
+ self?.selectAPIViewController.hideActivityIndicator()
+ })
+ case .failure(let error):
+ print("❌ Setting document for review failed: \(String(describing: error))")
self.selectAPIViewController.hideActivityIndicator()
}
- case .failure(let error):
- print("❌ Document creation failed: \(String(describing: error))")
- self.selectAPIViewController.hideActivityIndicator()
}
+ case .failure(let error):
+ print("❌ Document creation failed: \(String(describing: error))")
+ self.selectAPIViewController.hideActivityIndicator()
}
- case .failure(let error):
- print("❌ Document creation failed: \(String(describing: error))")
- self.selectAPIViewController.hideActivityIndicator()
}
+ case .failure(let error):
+ print("❌ Document creation failed: \(String(describing: error))")
+ self.selectAPIViewController.hideActivityIndicator()
}
}
}
@@ -278,6 +266,26 @@ final class AppCoordinator: Coordinator {
self.remove(childCoordinator: coordinator)
}
}
+
+ fileprivate func showInvoicesList(invoices: [DocumentWithExtractions]? = nil) {
+ self.selectAPIViewController.hideActivityIndicator()
+ let configuration = GiniHealthConfiguration()
+
+ // Show the close button to dismiss the payment review screen
+ configuration.showPaymentReviewCloseButton = true
+ configuration.paymentReviewStatusBarStyle = .lightContent
+
+ health.setConfiguration(configuration)
+
+ let invoicesListCoordinator = InvoicesListCoordinator()
+ paymentComponentsController = PaymentComponentsController(giniHealth: health)
+ invoicesListCoordinator.start(documentService: health.documentService,
+ hardcodedInvoicesController: HardcodedInvoicesController(),
+ paymentComponentsController: paymentComponentsController,
+ invoices: invoices)
+ add(childCoordinator: invoicesListCoordinator)
+ rootViewController.present(invoicesListCoordinator.rootViewController, animated: true)
+ }
}
// MARK: SelectAPIViewControllerDelegate
@@ -291,6 +299,8 @@ extension AppCoordinator: SelectAPIViewControllerDelegate {
break
case .paymentReview:
showPaymentReviewWithTestDocument()
+ case .invoicesList:
+ showInvoicesList()
}
}
}
@@ -302,6 +312,10 @@ extension AppCoordinator: ScreenAPICoordinatorDelegate {
coordinator.rootViewController.dismiss(animated: true)
self.remove(childCoordinator: coordinator)
}
+
+ func presentInvoicesList(invoices: [DocumentWithExtractions]?) {
+ self.showInvoicesList(invoices: invoices)
+ }
}
// MARK: GiniHealthDelegate
@@ -321,14 +335,28 @@ extension AppCoordinator: GiniHealthDelegate {
extension AppCoordinator: GiniHealthTrackingDelegate {
func onPaymentReviewScreenEvent(event: TrackingEvent) {
switch event.type {
- case .onNextButtonClicked:
- print("📝 Next button was tapped,\(String(describing: event.info))")
+ case .onToTheBankButtonClicked:
+ print("📝 To the banking app button was tapped,\(String(describing: event.info))")
case .onCloseButtonClicked:
print("📝 Close screen was triggered")
case .onCloseKeyboardButtonClicked:
print("📝 Close keyboard was triggered")
- case .onBankSelectionButtonClicked:
- print("📝 Bank selection button was tapped,\(String(describing: event.info))")
}
}
}
+
+// MARK: PaymentComponentControllerDelegate
+
+extension AppCoordinator: PaymentComponentsControllerProtocol {
+ func isLoadingStateChanged(isLoading: Bool) {
+ if isLoading {
+ selectAPIViewController.showActivityIndicator()
+ } else {
+ selectAPIViewController.hideActivityIndicator()
+ }
+ }
+
+ func didFetchedPaymentProviders() {
+ //
+ }
+}
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppDelegate.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppDelegate.swift
index 191a2f1f0..db8c4a0f8 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppDelegate.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/AppDelegate.swift
@@ -2,7 +2,7 @@
// AppDelegate.swift
// Example Swift
//
-// Created by Nadya Karaban on 26.03.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/deutscheBankIcon.imageset/Bank Logo.pdf b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/deutscheBankIcon.imageset/Bank Logo.pdf
new file mode 100644
index 000000000..39ee8d437
Binary files /dev/null and b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/deutscheBankIcon.imageset/Bank Logo.pdf differ
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/deutscheBankIcon.imageset/Contents.json b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/deutscheBankIcon.imageset/Contents.json
new file mode 100644
index 000000000..cedde6eb2
--- /dev/null
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/deutscheBankIcon.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Bank Logo.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/kreditBankIcon.imageset/Bank Logo.pdf b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/kreditBankIcon.imageset/Bank Logo.pdf
new file mode 100644
index 000000000..9855296d3
Binary files /dev/null and b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/kreditBankIcon.imageset/Bank Logo.pdf differ
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/kreditBankIcon.imageset/Contents.json b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/kreditBankIcon.imageset/Contents.json
new file mode 100644
index 000000000..cedde6eb2
--- /dev/null
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/kreditBankIcon.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Bank Logo.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/sparkasseBankIcon.imageset/Bank Logo.pdf b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/sparkasseBankIcon.imageset/Bank Logo.pdf
new file mode 100644
index 000000000..0b9905d55
Binary files /dev/null and b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/sparkasseBankIcon.imageset/Bank Logo.pdf differ
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/sparkasseBankIcon.imageset/Contents.json b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/sparkasseBankIcon.imageset/Contents.json
new file mode 100644
index 000000000..cedde6eb2
--- /dev/null
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Assets.xcassets/sparkasseBankIcon.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "Bank Logo.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Coordinator.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Coordinator.swift
index d85fbe643..2a9e8a6dd 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Coordinator.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Coordinator.swift
@@ -6,7 +6,6 @@
// Copyright © 2017 Gini GmbH. All rights reserved.
//
-import Foundation
import UIKit
protocol Coordinator: AnyObject {
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/CredentialsManager.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/CredentialsManager.swift
index f9a34703f..7475182a0 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/CredentialsManager.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/CredentialsManager.swift
@@ -2,7 +2,7 @@
// CredentialsManager.swift
// Example Swift
//
-// Created by Nadya Karaban on 16.04.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import Foundation
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/HealthNetworkingService.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/HealthNetworkingService.swift
index aec91f54e..a77f87aa1 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/HealthNetworkingService.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/HealthNetworkingService.swift
@@ -2,7 +2,7 @@
// HealthNetworkingService.swift
// GiniHealthSDKExample
//
-// Created by Nadya Karaban on 23.05.23.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import Foundation
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Info.plist b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Info.plist
index 1ddb8de7b..b1115e7c8 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Info.plist
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/Info.plist
@@ -57,8 +57,14 @@
LSApplicationQueriesSchemes
+ ginipay-insurance-bank-mock
+ ginipay-cbwm
+ ginipay-consorsbank
ginipay-bank
ginipay-ginibank
+ ginipay-consorsbank
+ ginipay-cbwm
+
UIApplicationSceneManifest
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/HardcodedInvoices.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/HardcodedInvoices.swift
new file mode 100644
index 000000000..b68fcbe45
--- /dev/null
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/HardcodedInvoices.swift
@@ -0,0 +1,78 @@
+//
+// HardcodedInvoicesController.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import Foundation
+import GiniHealthAPILibrary
+
+protocol HardcodedInvoicesControllerProtocol: AnyObject {
+ func obtainInvoicePhotosHardcoded(completion: @escaping (([Data]) -> Void))
+ func storeInvoicesWithExtractions(invoices: [DocumentWithExtractions])
+ func getInvoicesWithExtractions() -> [DocumentWithExtractions]
+ func appendInvoiceWithExtractions(invoice: DocumentWithExtractions)
+}
+
+final class HardcodedInvoicesController: HardcodedInvoicesControllerProtocol {
+ func obtainInvoicePhotosHardcoded(completion: @escaping (([Data]) -> Void)) {
+ var invoicesData: [Data] = []
+ for i in 1 ... Constants.numberOfInovices {
+ let invoiceTitle = "\(Constants.invoiceTitle)\(i)"
+ if let fileURL = Bundle.main.url(forResource: invoiceTitle, withExtension: Constants.invoiceFileFormat) {
+ do {
+ let invoiceData = try Data(contentsOf: fileURL)
+ invoicesData.append(invoiceData)
+ } catch {
+ Log("Couldn't load data from \(invoiceTitle). Error: \(error.localizedDescription)", event: .error)
+ }
+ } else {
+ Log("Invoice with name \(invoiceTitle) doesn't exist.", event: .warning)
+ }
+ }
+ Log("Successfully obtained \(invoicesData.count) invoices data", event: .success)
+ completion(invoicesData)
+ }
+
+
+ func storeInvoicesWithExtractions(invoices: [DocumentWithExtractions]) {
+ do {
+ let encoder = JSONEncoder()
+ let data = try encoder.encode(invoices)
+ UserDefaults.standard.set(data, forKey: Constants.storedInvoicesKey)
+ Log("Successfully stored invoices in UserDefaults", event: .success)
+ } catch {
+ Log("Unable to Encode Invoices: (\(error))", event: .error)
+ }
+ }
+
+ func getInvoicesWithExtractions() -> [DocumentWithExtractions] {
+ if let data = UserDefaults.standard.data(forKey: Constants.storedInvoicesKey) {
+ do {
+ let decoder = JSONDecoder()
+ let invoices = try decoder.decode([DocumentWithExtractions].self, from: data)
+ Log("Successfully obtained invoices from UserDefaults", event: .success)
+ return invoices
+ } catch {
+ Log("Unable to Decode Notes (\(error))", event: .error)
+ }
+ }
+ return []
+ }
+
+ func appendInvoiceWithExtractions(invoice: DocumentWithExtractions) {
+ var storedInvoices = getInvoicesWithExtractions()
+ storedInvoices.append(invoice)
+ storeInvoicesWithExtractions(invoices: storedInvoices)
+ }
+}
+
+extension HardcodedInvoicesController {
+ private enum Constants {
+ static let numberOfInovices = 5
+ static let invoiceTitle = "health-invoice-"
+ static let invoiceFileFormat = "jpg"
+ static let storedInvoicesKey = "giniHealthSDKExample.invoicesWithExtractions"
+ }
+}
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-1.jpg b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-1.jpg
new file mode 100644
index 000000000..a8363a732
Binary files /dev/null and b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-1.jpg differ
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-2.jpg b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-2.jpg
new file mode 100644
index 000000000..fd21b4093
Binary files /dev/null and b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-2.jpg differ
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-3.jpg b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-3.jpg
new file mode 100644
index 000000000..9bea15a65
Binary files /dev/null and b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-3.jpg differ
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-4.jpg b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-4.jpg
new file mode 100644
index 000000000..14768874b
Binary files /dev/null and b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-4.jpg differ
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-5.jpg b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-5.jpg
new file mode 100644
index 000000000..1d61bfacd
Binary files /dev/null and b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/HardcodedInvoices/health-invoice-5.jpg differ
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoiceTableViewCell.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoiceTableViewCell.swift
new file mode 100644
index 000000000..aa55e9d2d
--- /dev/null
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoiceTableViewCell.swift
@@ -0,0 +1,45 @@
+//
+// InvoiceTableViewCell.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+final class InvoiceTableViewCell: UITableViewCell {
+
+ static let identifier = "InvoiceTableViewCell"
+
+ var cellViewModel: InvoiceTableViewCellModel? {
+ didSet {
+ recipientLabel.text = cellViewModel?.recipientNameText
+ dueDateLabel.text = cellViewModel?.dueDateText
+ amountLabel.text = cellViewModel?.amountToPayText
+
+ recipientLabel.isHidden = cellViewModel?.isRecipientLabelHidden ?? false
+ dueDateLabel.isHidden = cellViewModel?.isDueDataLabelHidden ?? false
+
+ if cellViewModel?.shouldShowPaymentComponent ?? false, let paymentComponentView = cellViewModel?.paymentComponentView {
+ mainStackView.addArrangedSubview(paymentComponentView)
+ }
+ }
+ }
+
+ @IBOutlet private weak var mainStackView: UIStackView!
+ @IBOutlet private weak var recipientLabel: UILabel!
+ @IBOutlet private weak var dueDateLabel: UILabel!
+ @IBOutlet private weak var amountLabel: UILabel!
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+ selectionStyle = .none
+ }
+
+ override func prepareForReuse() {
+ super.prepareForReuse()
+ if mainStackView.arrangedSubviews.count > 1 {
+ mainStackView.arrangedSubviews.last?.removeFromSuperview()
+ }
+ }
+}
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoiceTableViewCell.xib b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoiceTableViewCell.xib
new file mode 100644
index 000000000..058d18a36
--- /dev/null
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoiceTableViewCell.xib
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoiceTableViewCellModel.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoiceTableViewCellModel.swift
new file mode 100644
index 000000000..b1d4fcce4
--- /dev/null
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoiceTableViewCellModel.swift
@@ -0,0 +1,53 @@
+//
+// InvoiceTableViewCellModel.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import Foundation
+import GiniHealthAPILibrary
+import GiniHealthSDK
+import UIKit
+
+final class InvoiceTableViewCellModel {
+ private var invoice: DocumentWithExtractions
+ private var paymentComponentsController: PaymentComponentsController
+
+ init(invoice: DocumentWithExtractions,
+ paymentComponentsController: PaymentComponentsController) {
+ self.invoice = invoice
+ self.paymentComponentsController = paymentComponentsController
+ }
+
+ var recipientNameText: String {
+ invoice.recipient ?? ""
+ }
+
+ var amountToPayText: String {
+ if let amoountToPay = invoice.amountToPay, let amountToPayFormatted = Price(extractionString: amoountToPay) {
+ return amountToPayFormatted.string ?? ""
+ }
+ return ""
+ }
+
+ var dueDateText: String {
+ invoice.paymentDueDate ?? ""
+ }
+
+ var isDueDataLabelHidden: Bool {
+ dueDateText.isEmpty
+ }
+
+ var isRecipientLabelHidden: Bool {
+ recipientNameText.isEmpty
+ }
+
+ var shouldShowPaymentComponent: Bool {
+ invoice.isPayable ?? false
+ }
+
+ var paymentComponentView: UIView {
+ return paymentComponentsController.paymentView(documentId: invoice.documentID)
+ }
+}
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoicesListCoordinator.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoicesListCoordinator.swift
new file mode 100644
index 000000000..1832747b6
--- /dev/null
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoicesListCoordinator.swift
@@ -0,0 +1,36 @@
+//
+// InvoicesListCoordinator.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+import GiniHealthAPILibrary
+import GiniHealthSDK
+
+final class InvoicesListCoordinator: NSObject, Coordinator {
+
+ var childCoordinators: [Coordinator] = []
+ var rootViewController: UIViewController {
+ return invoicesListNavigationController
+ }
+
+ var invoicesListNavigationController: UINavigationController!
+ var invoicesListViewController: InvoicesListViewController!
+
+ func start(documentService: DefaultDocumentService,
+ hardcodedInvoicesController: HardcodedInvoicesControllerProtocol,
+ paymentComponentsController: PaymentComponentsController,
+ invoices: [DocumentWithExtractions]? = nil) {
+ self.invoicesListViewController = InvoicesListViewController()
+ invoicesListViewController.viewModel = InvoicesListViewModel(coordinator: self,
+ invoices: invoices,
+ documentService: documentService,
+ hardcodedInvoicesController: hardcodedInvoicesController,
+ paymentComponentsController: paymentComponentsController)
+ invoicesListNavigationController = RootNavigationController(rootViewController: invoicesListViewController)
+ invoicesListNavigationController.modalPresentationStyle = .fullScreen
+ invoicesListNavigationController.interactivePopGestureRecognizer?.delegate = nil
+ }
+}
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoicesListViewController.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoicesListViewController.swift
new file mode 100644
index 000000000..a84234e52
--- /dev/null
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoicesListViewController.swift
@@ -0,0 +1,169 @@
+//
+//
+// InvoicesListViewController.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+
+protocol InvoicesListViewControllerProtocol: AnyObject {
+ func showActivityIndicator()
+ func hideActivityIndicator()
+ func reloadTableView()
+ func showErrorAlertView(error: String)
+}
+
+final class InvoicesListViewController: UIViewController {
+
+ // MARK: - Variables
+ private lazy var invoicesTableView: UITableView = {
+ let tableView = UITableView(frame: .zero, style: .plain)
+ tableView.translatesAutoresizingMaskIntoConstraints = false
+ tableView.dataSource = self
+ tableView.delegate = self
+ tableView.register(UINib(nibName: InvoiceTableViewCell.identifier,
+ bundle: nil),
+ forCellReuseIdentifier: InvoiceTableViewCell.identifier)
+ tableView.contentInset = UIEdgeInsets(top: Constants.padding,
+ left: 0,
+ bottom: Constants.padding,
+ right: 0)
+ tableView.separatorColor = viewModel.tableViewSeparatorColor
+ tableView.estimatedRowHeight = Constants.rowHeight
+ tableView.rowHeight = UITableView.automaticDimension
+ tableView.tableFooterView = UIView()
+ return tableView
+ }()
+
+ private lazy var activityIndicator: UIActivityIndicatorView = {
+ let activityIndicator = UIActivityIndicatorView()
+ if #available(iOS 13.0, *) {
+ activityIndicator.style = .large
+ } else {
+ activityIndicator.style = .gray
+ }
+ activityIndicator.center = view.center
+ activityIndicator.hidesWhenStopped = true
+ return activityIndicator
+ }()
+
+ var viewModel: InvoicesListViewModel!
+
+ // MARK: - Functions
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ viewModel.viewDidLoad()
+ }
+
+ override func loadView() {
+ super.loadView()
+ title = viewModel.titleText
+ view.backgroundColor = viewModel.backgroundColor
+ setupTableView()
+ setupNavigationBar()
+ }
+
+ private func setupTableView() {
+ if #available(iOS 15.0, *) {
+ invoicesTableView.sectionHeaderTopPadding = 0
+ }
+ view.addSubview(invoicesTableView)
+
+ NSLayoutConstraint.activate([
+ invoicesTableView.topAnchor.constraint(equalTo: view.topAnchor,
+ constant: Constants.padding * 2),
+ invoicesTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor,
+ constant: Constants.padding * 2),
+ invoicesTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor,
+ constant: -Constants.padding * 2),
+ invoicesTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,
+ constant: -Constants.padding * 2)
+ ])
+ }
+
+ private func setupNavigationBar() {
+ let uploadInvoiceItem = UIBarButtonItem(title: viewModel.uploadInvoicesText,
+ style: .plain,
+ target: self,
+ action: #selector(uploadInvoicesButtonTapped))
+ self.navigationItem.rightBarButtonItem = uploadInvoiceItem
+
+ let cancelItem = UIBarButtonItem(title: viewModel.cancelText,
+ style: .plain,
+ target: self,
+ action: #selector(dismissViewControllerTapped))
+ self.navigationItem.leftBarButtonItem = cancelItem
+ }
+
+ @objc func uploadInvoicesButtonTapped() {
+ viewModel.uploadInvoices()
+ }
+
+ @objc func dismissViewControllerTapped() {
+ self.dismiss(animated: true)
+ }
+}
+
+extension InvoicesListViewController: UITableViewDelegate, UITableViewDataSource {
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return viewModel.invoices.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ guard let cell = tableView.dequeueReusableCell(withIdentifier: InvoiceTableViewCell.identifier, for: indexPath) as? InvoiceTableViewCell else {
+ return UITableViewCell()
+ }
+ let invoiceTableViewCellModel = viewModel.invoices.map { InvoiceTableViewCellModel(invoice: $0,
+ paymentComponentsController: viewModel.paymentComponentsController) }[indexPath.row]
+ cell.cellViewModel = invoiceTableViewCellModel
+ return cell
+ }
+
+ func numberOfSections(in tableView: UITableView) -> Int {
+ if viewModel.invoices.isEmpty {
+ let label = UILabel()
+ label.text = viewModel.noInvoicesText
+ label.textAlignment = .center
+ tableView.backgroundView = label
+ tableView.separatorStyle = .none
+ return 0
+ } else {
+ tableView.backgroundView = nil
+ tableView.separatorStyle = .singleLine
+ return 1
+ }
+ }
+}
+
+extension InvoicesListViewController: InvoicesListViewControllerProtocol {
+ func showActivityIndicator() {
+ self.activityIndicator.startAnimating()
+ self.view.addSubview(self.activityIndicator)
+ }
+
+ func hideActivityIndicator() {
+ self.activityIndicator.stopAnimating()
+ }
+
+ func reloadTableView() {
+ self.invoicesTableView.reloadData()
+ }
+
+ func showErrorAlertView(error: String) {
+ let alertController = UIAlertController(title: viewModel.errorTitleText,
+ message: error,
+ preferredStyle: .alert)
+ alertController.addAction(UIAlertAction(title: "Ok", style: .default))
+ self.present(alertController, animated: true)
+ }
+}
+
+extension InvoicesListViewController {
+ private enum Constants {
+ static let padding: CGFloat = 8
+ static let cornerRadius: CGFloat = 16
+ static let rowHeight: CGFloat = 40
+ }
+}
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoicesListViewModel.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoicesListViewModel.swift
new file mode 100644
index 000000000..64d4a3aae
--- /dev/null
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/InvoicesList/InvoicesListViewModel.swift
@@ -0,0 +1,239 @@
+//
+// InvoicesListViewModel.swift
+//
+// Copyright © 2024 Gini GmbH. All rights reserved.
+//
+
+
+import UIKit
+import GiniHealthAPILibrary
+import GiniCaptureSDK
+import GiniHealthSDK
+
+struct DocumentWithExtractions: Codable {
+ var documentID: String
+ var amountToPay: String?
+ var paymentDueDate: String?
+ var recipient: String?
+ var isPayable: Bool?
+
+ init(documentID: String, extractionResult: GiniHealthAPILibrary.ExtractionResult) {
+ self.documentID = documentID
+ self.amountToPay = extractionResult.payment?.first?.first(where: {$0.name == "amount_to_pay"})?.value
+ self.paymentDueDate = extractionResult.extractions.first(where: {$0.name == "payment_due_date"})?.value
+ self.recipient = extractionResult.payment?.first?.first(where: {$0.name == "payment_recipient"})?.value
+ }
+
+ init(documentID: String, extractions: [GiniHealthAPILibrary.Extraction], isPayable: Bool) {
+ self.documentID = documentID
+ self.amountToPay = extractions.first(where: {$0.name == "amount_to_pay"})?.value
+ self.paymentDueDate = extractions.first(where: {$0.name == "payment_due_date"})?.value
+ self.recipient = extractions.first(where: {$0.name == "payment_recipient"})?.value
+ self.isPayable = isPayable
+ }
+}
+
+final class InvoicesListViewModel {
+
+ private let coordinator: InvoicesListCoordinator
+ private var documentService: GiniHealthAPILibrary.DefaultDocumentService
+
+ private let hardcodedInvoicesController: HardcodedInvoicesControllerProtocol
+ var paymentComponentsController: PaymentComponentsController
+
+ var invoices: [DocumentWithExtractions]
+
+ let noInvoicesText = NSLocalizedString("giniHealthSDKExample.invoicesList.missingInvoices.text", comment: "")
+ let titleText = NSLocalizedString("giniHealthSDKExample.invoicesList.title", comment: "")
+ let uploadInvoicesText = NSLocalizedString("giniHealthSDKExample.uploadInvoices.button.title", comment: "")
+ let cancelText = NSLocalizedString("giniHealthSDKExample.cancel.button.title", comment: "")
+ let errorTitleText = NSLocalizedString("giniHealthSDKExample.invoicesList.error", comment: "")
+
+ let backgroundColor: UIColor = GiniColor(light: .white,
+ dark: .black).uiColor()
+ let tableViewSeparatorColor: UIColor = GiniColor(light: .lightGray,
+ dark: .darkGray).uiColor()
+
+ private let tableViewCell: UITableViewCell.Type = InvoiceTableViewCell.self
+ private var errors: [String] = []
+
+ let dispatchGroup = DispatchGroup()
+
+ init(coordinator: InvoicesListCoordinator,
+ invoices: [DocumentWithExtractions]? = nil,
+ documentService: GiniHealthAPILibrary.DefaultDocumentService,
+ hardcodedInvoicesController: HardcodedInvoicesControllerProtocol,
+ paymentComponentsController: PaymentComponentsController) {
+ self.coordinator = coordinator
+ self.hardcodedInvoicesController = hardcodedInvoicesController
+ self.invoices = invoices ?? hardcodedInvoicesController.getInvoicesWithExtractions()
+ self.documentService = documentService
+ self.paymentComponentsController = paymentComponentsController
+ self.paymentComponentsController.delegate = self
+ self.paymentComponentsController.viewDelegate = self
+ self.paymentComponentsController.bottomViewDelegate = self
+ }
+
+ func viewDidLoad() {
+ paymentComponentsController.loadPaymentProviders()
+ }
+
+ private func setDispatchGroupNotifier() {
+ dispatchGroup.notify(queue: .main) {
+ self.showErrorsIfAny()
+ if !self.invoices.isEmpty {
+ self.hardcodedInvoicesController.storeInvoicesWithExtractions(invoices: self.invoices)
+ self.coordinator.invoicesListViewController?.hideActivityIndicator()
+ self.coordinator.invoicesListViewController?.reloadTableView()
+ }
+ }
+ }
+
+ private func showErrorsIfAny() {
+ if !errors.isEmpty {
+ let uniqueErrorMessages = Array(Set(errors))
+ DispatchQueue.main.async {
+ self.coordinator.invoicesListViewController.showErrorAlertView(error: uniqueErrorMessages.joined(separator: ", "))
+ }
+ errors = []
+ }
+ }
+
+ @objc
+ func uploadInvoices() {
+ coordinator.invoicesListViewController?.showActivityIndicator()
+ hardcodedInvoicesController.obtainInvoicePhotosHardcoded { [weak self] invoicesData in
+ if !invoicesData.isEmpty {
+ self?.uploadDocuments(dataDocuments: invoicesData)
+ } else {
+ self?.coordinator.invoicesListViewController.hideActivityIndicator()
+ }
+ }
+ setDispatchGroupNotifier()
+ }
+
+ private func uploadDocuments(dataDocuments: [Data]) {
+ for giniDocument in dataDocuments {
+ dispatchGroup.enter()
+ self.documentService.createDocument(fileName: nil,
+ docType: .invoice,
+ type: .partial(giniDocument),
+ metadata: nil) { [weak self] result in
+ switch result {
+ case .success(let createdDocument):
+ Log("Successfully created document with id: \(createdDocument.id)", event: .success)
+ self?.documentService.extractions(for: createdDocument,
+ cancellationToken: CancellationToken()) { [weak self] result in
+ switch result {
+ case let .success(extractionResult):
+ Log("Successfully fetched extractions for id: \(createdDocument.id)", event: .success)
+ self?.invoices.append(DocumentWithExtractions(documentID: createdDocument.id,
+ extractionResult: extractionResult))
+ self?.paymentComponentsController.checkIfDocumentIsPayable(docId: createdDocument.id, completion: { [weak self] result in
+ switch result {
+ case let .success(isPayable):
+ Log("Successfully checked if document \(createdDocument.id) is payable", event: .success)
+ if let indexDocument = self?.invoices.firstIndex(where: { $0.documentID == createdDocument.id }) {
+ self?.invoices[indexDocument].isPayable = isPayable
+ }
+ case let .failure(error):
+ Log("Checking if document \(createdDocument.id) is payable failed with error: \(String(describing: error))", event: .error)
+ self?.errors.append(error.localizedDescription)
+ }
+ self?.dispatchGroup.leave()
+ })
+ case let .failure(error):
+ Log("Obtaining extractions from document with id \(createdDocument.id) failed with error: \(String(describing: error))", event: .error)
+ self?.errors.append(error.message)
+ self?.dispatchGroup.leave()
+ }
+ }
+ case .failure(let error):
+ Log("Document creation failed: \(String(describing: error))", event: .error)
+ self?.errors.append(error.message)
+ self?.dispatchGroup.leave()
+ }
+ }
+ }
+ }
+}
+
+extension InvoicesListViewModel: PaymentComponentViewProtocol {
+
+ func didTapOnMoreInformation(documentId: String?) {
+ guard let documentId else { return }
+ Log("Tapped on More Information on :\(documentId)", event: .success)
+ let paymentInfoViewController = paymentComponentsController.paymentInfoViewController()
+ self.coordinator.invoicesListViewController.navigationController?.pushViewController(paymentInfoViewController, animated: true)
+ }
+
+ func didTapOnBankPicker(documentId: String?) {
+ guard let documentId else { return }
+ Log("Tapped on Bank Picker on :\(documentId)", event: .success)
+ let bankSelectionBottomSheet = paymentComponentsController.bankSelectionBottomSheet()
+ bankSelectionBottomSheet.modalPresentationStyle = .overFullScreen
+ self.coordinator.invoicesListViewController.present(bankSelectionBottomSheet, animated: true)
+ }
+
+ func didTapOnPayInvoice(documentId: String?) {
+ guard let documentId else { return }
+ Log("Tapped on Pay Invoice on :\(documentId)", event: .success)
+ paymentComponentsController.loadPaymentReviewScreenFor(documentID: documentId, trackingDelegate: self) { [weak self] viewController, error in
+ if let error {
+ self?.errors.append(error.localizedDescription)
+ self?.showErrorsIfAny()
+ } else if let viewController {
+ viewController.modalTransitionStyle = .coverVertical
+ viewController.modalPresentationStyle = .overCurrentContext
+ self?.coordinator.invoicesListViewController.present(viewController, animated: true)
+ }
+ }
+ }
+}
+
+extension InvoicesListViewModel: PaymentComponentsControllerProtocol {
+ func didFetchedPaymentProviders() {
+ DispatchQueue.main.async {
+ self.coordinator.invoicesListViewController.reloadTableView()
+ }
+ }
+
+ func isLoadingStateChanged(isLoading: Bool) {
+ DispatchQueue.main.async {
+ if isLoading {
+ self.coordinator.invoicesListViewController.showActivityIndicator()
+ } else {
+ self.coordinator.invoicesListViewController.hideActivityIndicator()
+ }
+ }
+ }
+}
+
+extension InvoicesListViewModel: PaymentProvidersBottomViewProtocol {
+ func didSelectPaymentProvider(paymentProvider: PaymentProvider) {
+ DispatchQueue.main.async {
+ self.coordinator.invoicesListViewController.presentedViewController?.dismiss(animated: true)
+ self.hardcodedInvoicesController.storeInvoicesWithExtractions(invoices: self.invoices)
+ self.coordinator.invoicesListViewController.reloadTableView()
+ }
+ }
+
+ func didTapOnClose() {
+ DispatchQueue.main.async {
+ self.coordinator.invoicesListViewController.presentedViewController?.dismiss(animated: true)
+ }
+ }
+}
+
+extension InvoicesListViewModel: GiniHealthTrackingDelegate {
+ func onPaymentReviewScreenEvent(event: TrackingEvent) {
+ switch event.type {
+ case .onToTheBankButtonClicked:
+ Log("To the banking app button was tapped,\(String(describing: event.info))", event: .success)
+ case .onCloseButtonClicked:
+ Log("Close screen was triggered", event: .success)
+ case .onCloseKeyboardButtonClicked:
+ Log("Close keyboard was triggered", event: .success)
+ }
+ }
+}
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/RootNavigationController.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/RootNavigationController.swift
index 347895843..2782dd09d 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/RootNavigationController.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/RootNavigationController.swift
@@ -2,7 +2,7 @@
// RootNavigationController.swift
// GiniHealthSDKExample
//
-// Created by Nadya Karaban on 22.05.23.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/SceneDelegate.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/SceneDelegate.swift
index c7085e603..df2de81cf 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/SceneDelegate.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/SceneDelegate.swift
@@ -2,7 +2,7 @@
// SceneDelegate.swift
// GiniHealthSDKExample
//
-// Created by Nadya Karaban on 10.11.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/ScreenAPICoordinator.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/ScreenAPICoordinator.swift
index cab410594..d80b84f27 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/ScreenAPICoordinator.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/ScreenAPICoordinator.swift
@@ -2,7 +2,7 @@
// ScreenAPICoordinator.swift
// GiniHealthSDKExample
//
-// Created by Nadya Karaban on 22.05.23.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import GiniBankAPILibrary
@@ -13,6 +13,7 @@ import UIKit
protocol ScreenAPICoordinatorDelegate: AnyObject {
func screenAPI(coordinator: ScreenAPICoordinator, didFinish: ())
+ func presentInvoicesList(invoices: [DocumentWithExtractions]?)
}
final class ScreenAPICoordinator: NSObject, Coordinator, GiniHealthTrackingDelegate, GiniCaptureResultsDelegate {
@@ -32,6 +33,8 @@ final class ScreenAPICoordinator: NSObject, Coordinator, GiniHealthTrackingDeleg
var visionDocuments: [GiniCaptureDocument]?
var visionConfiguration: GiniConfiguration
private var captureExtractedResults: [GiniBankAPILibrary.Extraction] = []
+ private var hardcodedInvoicesController: HardcodedInvoicesController
+ private var paymentComponentController: PaymentComponentsController
// {extraction name} : {entity name}
private let editableSpecificExtractions = ["paymentRecipient" : "companyname", "paymentReference" : "reference", "paymentPurpose" : "text", "iban" : "iban", "bic" : "bic", "amountToPay" : "amount"]
@@ -39,11 +42,15 @@ final class ScreenAPICoordinator: NSObject, Coordinator, GiniHealthTrackingDeleg
init(configuration: GiniConfiguration,
importedDocuments documents: [GiniCaptureDocument]?,
client: GiniHealthAPILibrary.Client,
- documentMetadata: GiniHealthAPILibrary.Document.Metadata?) {
+ documentMetadata: GiniHealthAPILibrary.Document.Metadata?,
+ hardcodedInvoicesController: HardcodedInvoicesController,
+ paymentComponentController: PaymentComponentsController) {
visionConfiguration = configuration
visionDocuments = documents
self.client = client
self.documentMetadata = documentMetadata
+ self.hardcodedInvoicesController = hardcodedInvoicesController
+ self.paymentComponentController = paymentComponentController
super.init()
}
@@ -77,18 +84,29 @@ final class ScreenAPICoordinator: NSObject, Coordinator, GiniHealthTrackingDeleg
for extraction in captureExtractedResults {
healthExtractions.append(GiniHealthAPILibrary.Extraction(box: nil, candidates: extraction.candidates, entity: extraction.entity, value: extraction.value, name: extraction.name))
}
-
+
if let healthSdk = self.giniHealth, let docId = result.document?.id {
// this step needed since we've got 2 different Document structures
- healthSdk.fetchDataForReview(documentId: docId) { result in
- switch result {
+ healthSdk.fetchDataForReview(documentId: docId) { [weak self] resultReview in
+ switch resultReview {
case .success(let data):
- let vc = PaymentReviewViewController.instantiate(with: healthSdk, data: data, trackingDelegate: self)
- vc.modalTransitionStyle = .coverVertical
- vc.modalPresentationStyle = .overCurrentContext
- self.rootViewController.present(vc, animated: true)
+ // Store invoice/document into Invoices list
+ self?.giniHealth?.checkIfDocumentIsPayable(docId: result.document?.id ?? "", completion: { [weak self] resultPayable in
+ switch resultPayable {
+ case .success(let isPayable):
+ let invoice = DocumentWithExtractions(documentID: result.document?.id ?? "",
+ extractions: data.extractions,
+ isPayable: isPayable)
+ self?.hardcodedInvoicesController.appendInvoiceWithExtractions(invoice: invoice)
+ self?.rootViewController.dismiss(animated: true, completion: {
+ self?.delegate?.presentInvoicesList(invoices: [invoice])
+ })
+ case .failure(let error):
+ print("❌ Checking if document is payable failed: \(String(describing: error))")
+ }
+ })
case .failure(let error):
- print("❌ Document data fetching failed: \(String(describing: error))")
+ print("❌ Document data fetching failed: \(String(describing: error))")
}
}
}
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/SelectAPIViewController.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/SelectAPIViewController.swift
index 50008e1df..4bfe00fdf 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/SelectAPIViewController.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/SelectAPIViewController.swift
@@ -21,6 +21,7 @@ enum GiniCaptureAPIType {
case screen
case component
case paymentReview
+ case invoicesList
}
/**
@@ -29,10 +30,11 @@ enum GiniCaptureAPIType {
*/
final class SelectAPIViewController: UIViewController {
- @IBOutlet weak var metaInformationButton: UIButton!
- @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
- @IBOutlet weak var startWithTestDocumentButton: UIButton!
- @IBOutlet weak var startWithGiniCaptureButton: UIButton!
+ @IBOutlet private weak var metaInformationButton: UIButton!
+ @IBOutlet private weak var activityIndicator: UIActivityIndicatorView!
+ @IBOutlet private weak var startWithTestDocumentButton: UIButton!
+ @IBOutlet private weak var startWithGiniCaptureButton: UIButton!
+ @IBOutlet private weak var invoicesListButton: UIButton!
weak var delegate: SelectAPIViewControllerDelegate?
@@ -62,6 +64,10 @@ final class SelectAPIViewController: UIViewController {
delegate?.selectAPI(viewController: self, didSelectApi: .paymentReview)
}
+ @IBAction func launchInvoicesList(_ sender: Any) {
+ delegate?.selectAPI(viewController: self, didSelectApi: .invoicesList)
+ }
+
func showActivityIndicator() {
DispatchQueue.main.async {
self.activityIndicator.startAnimating()
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/SelectAPIViewController.xib b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/SelectAPIViewController.xib
index b3e1f5116..73fa326c3 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/SelectAPIViewController.xib
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/SelectAPIViewController.xib
@@ -1,9 +1,9 @@
-
+
-
+
@@ -12,6 +12,7 @@
+
@@ -98,6 +99,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -111,12 +132,14 @@
+
+
@@ -127,10 +150,10 @@
-
-
+
+
@@ -144,7 +167,7 @@
-
+
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/UIViewController+Utils.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/UIViewController+Utils.swift
index 3b6a9fd77..54bd2c795 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/UIViewController+Utils.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/UIViewController+Utils.swift
@@ -2,10 +2,9 @@
// UIViewController+Utils.swift
// Example Swift
//
-// Created by Nadya Karaban on 16.04.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
-import Foundation
import UIKit
extension UIViewController {
func showError(_ title: String? = nil, message: String) {
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/ViewController.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/ViewController.swift
index e7659582f..a290bf2c5 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/ViewController.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/ViewController.swift
@@ -2,7 +2,7 @@
// ViewController.swift
// GiniHealthSDKExample
//
-// Created by Nadya Karaban on 10.11.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/de.lproj/Localizable.strings b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/de.lproj/Localizable.strings
index dfa10bcce..54e9096a7 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/de.lproj/Localizable.strings
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/de.lproj/Localizable.strings
@@ -2,6 +2,13 @@
Localizable.strings
GiniHealthSDKExample
- Created by Nadya Karaban on 28.02.22.
-
+ // Copyright © 2024 Gini GmbH. All rights reserved.
+
*/
+
+"giniHealthSDKExample.invoicesList.title" = "Rechnungsliste";
+"giniHealthSDKExample.uploadInvoices.button.title" = "↑ Rechnungen";
+"giniHealthSDKExample.invoicesList.missingInvoices.text" = "Keine Rechnungen";
+"giniHealthSDKExample.invoicesList.error" = "Fehler";
+"giniHealthSDKExample.cancel.button.title" = "Abbrechen";
+
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/en.lproj/Localizable.strings b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/en.lproj/Localizable.strings
index dfa10bcce..e452c7038 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/en.lproj/Localizable.strings
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExample/en.lproj/Localizable.strings
@@ -2,6 +2,13 @@
Localizable.strings
GiniHealthSDKExample
- Created by Nadya Karaban on 28.02.22.
-
+ // Copyright © 2024 Gini GmbH. All rights reserved.
+
*/
+
+"giniHealthSDKExample.invoicesList.title" = "Invoices List";
+"giniHealthSDKExample.uploadInvoices.button.title" = "↑ Invoices";
+"giniHealthSDKExample.invoicesList.missingInvoices.text" = "No invoices";
+"giniHealthSDKExample.invoicesList.error" = "Error";
+"giniHealthSDKExample.cancel.button.title" = "Cancel";
+"ginihealth.paymentcomponent.paymentinfo.gini.privacypolicy.link" = "https://www.google.com/"; // This should be overwrited by client with their privacy policy link
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExampleTests/GiniHealthSDKExampleTests.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExampleTests/GiniHealthSDKExampleTests.swift
index 8989f3800..b2d9a1b2f 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExampleTests/GiniHealthSDKExampleTests.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKExampleTests/GiniHealthSDKExampleTests.swift
@@ -2,7 +2,7 @@
// GiniHealthSDKExampleTests.swift
// GiniHealthSDKExampleTests
//
-// Created by Nadya Karaban on 10.11.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import XCTest
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/AppDelegate.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/AppDelegate.swift
index 8f51dbd09..944e7054a 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/AppDelegate.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/AppDelegate.swift
@@ -2,7 +2,7 @@
// AppDelegate.swift
// HealthSDKPinningExample
//
-// Created by Nadya Karaban on 05.10.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/SceneDelegate.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/SceneDelegate.swift
index 706a83d53..19520cb5b 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/SceneDelegate.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/SceneDelegate.swift
@@ -2,7 +2,7 @@
// SceneDelegate.swift
// HealthSDKPinningExample
//
-// Created by Nadya Karaban on 05.10.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/ViewController.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/ViewController.swift
index ce23b0090..dc6409591 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/ViewController.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/ViewController.swift
@@ -2,7 +2,7 @@
// ViewController.swift
// HealthSDKPinningExample
//
-// Created by Nadya Karaban on 05.10.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import UIKit
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/de.lproj/Localizable.strings b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/de.lproj/Localizable.strings
index 5d10f8af5..e156caab4 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/de.lproj/Localizable.strings
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/de.lproj/Localizable.strings
@@ -2,6 +2,6 @@
Localizable.strings
HealthSDKPinningExample
- Created by Nadya Karaban on 07.10.21.
-
+ // Copyright © 2024 Gini GmbH. All rights reserved.
+
*/
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/en.lproj/Localizable.strings b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/en.lproj/Localizable.strings
index c63b13ac1..221fad5d9 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/en.lproj/Localizable.strings
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExample/en.lproj/Localizable.strings
@@ -2,7 +2,7 @@
Localizable.strings
HealthSDKPinningExample
- Created by Nadya Karaban on 07.10.21.
-
+ // Copyright © 2024 Gini GmbH. All rights reserved.
+
*/
"ginihealth.reviewscreen.iban.placeholder" = "IBANXXXX";
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExampleTests/GiniHealthSDKPinningExampleIntegrationTests.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExampleTests/GiniHealthSDKPinningExampleIntegrationTests.swift
index 2aa985891..b93e25934 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExampleTests/GiniHealthSDKPinningExampleIntegrationTests.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExampleTests/GiniHealthSDKPinningExampleIntegrationTests.swift
@@ -2,7 +2,7 @@
// GiniHealthSDKPinningExampleTests.swift
// GiniHealthSDKPinningExampleTests
//
-// Created by Nadya Karaban on 18.05.22.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import XCTest
diff --git a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExampleTests/GiniHealthSDKPinningExampleWrongCertificatesTests.swift b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExampleTests/GiniHealthSDKPinningExampleWrongCertificatesTests.swift
index 56fea600a..97c1c01f8 100644
--- a/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExampleTests/GiniHealthSDKPinningExampleWrongCertificatesTests.swift
+++ b/HealthSDK/GiniHealthSDKExample/GiniHealthSDKPinningExampleTests/GiniHealthSDKPinningExampleWrongCertificatesTests.swift
@@ -2,7 +2,7 @@
// GiniHealthSDKPinningExampleWrongCertificatesTests.swift
// GiniHealthSDKPinningExampleTests
//
-// Created by Nadya Karaban on 18.05.22.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
import XCTest
diff --git a/HealthSDK/GiniHealthSDKPinning/Package-release.swift b/HealthSDK/GiniHealthSDKPinning/Package-release.swift
index 711e59d62..25040226a 100644
--- a/HealthSDK/GiniHealthSDKPinning/Package-release.swift
+++ b/HealthSDK/GiniHealthSDKPinning/Package-release.swift
@@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "GiniHealthSDKPinning",
- platforms: [.iOS(.v12), .macOS(.v10_13)],
+ platforms: [.iOS(.v12)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
@@ -15,8 +15,8 @@ let package = Package(
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
- .package(name: "GiniHealthAPILibraryPinning", url: "https://github.com/gini/health-api-library-pinning-ios.git", .exact("3.0.1")),
- .package(name: "GiniHealthSDK", url: "https://github.com/gini/health-sdk-ios.git", .exact("3.0.1")),
+ .package(name: "GiniHealthAPILibraryPinning", url: "https://github.com/gini/health-api-library-pinning-ios.git", .exact("4.0.0")),
+ .package(name: "GiniHealthSDK", url: "https://github.com/gini/health-sdk-ios.git", .exact("4.0.0")),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
diff --git a/HealthSDK/GiniHealthSDKPinning/Package.swift b/HealthSDK/GiniHealthSDKPinning/Package.swift
index ef6947dde..5d9e4b9d2 100644
--- a/HealthSDK/GiniHealthSDKPinning/Package.swift
+++ b/HealthSDK/GiniHealthSDKPinning/Package.swift
@@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "GiniHealthSDKPinning",
- platforms: [.iOS(.v12), .macOS(.v10_13)],
+ platforms: [.iOS(.v12)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
diff --git a/HealthSDK/GiniHealthSDKPinning/Sources/GiniHealthSDKPinning/GiniHealthSDKPinningVersion.swift b/HealthSDK/GiniHealthSDKPinning/Sources/GiniHealthSDKPinning/GiniHealthSDKPinningVersion.swift
index c1e18c356..a7a5a0fda 100644
--- a/HealthSDK/GiniHealthSDKPinning/Sources/GiniHealthSDKPinning/GiniHealthSDKPinningVersion.swift
+++ b/HealthSDK/GiniHealthSDKPinning/Sources/GiniHealthSDKPinning/GiniHealthSDKPinningVersion.swift
@@ -2,7 +2,7 @@
// GiniHealthSDKPinningVersion.swift
//
//
-// Created by Nadya Karaban on 15.10.21.
+// Copyright © 2024 Gini GmbH. All rights reserved.
//
-public let GiniHealthSDKPinningVersion = "3.0.1"
+public let GiniHealthSDKPinningVersion = "4.0.0"