Skip to content

Commit

Permalink
Release version 3.9.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Team Mobile Schorsch committed Jul 31, 2024
1 parent aea1141 commit 211f58a
Show file tree
Hide file tree
Showing 62 changed files with 2,151 additions and 487 deletions.
5 changes: 3 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ let package = Package(
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(name: "GiniBankAPILibrary", url: "https://github.com/gini/bank-api-library-ios.git", .exact("3.1.3")),
.package(name: "GiniBankAPILibrary", url: "https://github.com/gini/bank-api-library-ios.git", .exact("3.2.0"))
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.

.target(
name: "GiniCaptureSDK",
dependencies: ["GiniBankAPILibrary"]),
dependencies: ["GiniBankAPILibrary"
]),
.testTarget(
name: "GiniCaptureSDKTests",
dependencies: ["GiniCaptureSDK"],
Expand Down
16 changes: 16 additions & 0 deletions Sources/GiniCaptureSDK/Core/Custom views/GiniBarButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public enum BarButtonType {
case help
case back(title: String)
case done
case skip
}

/**
Expand Down Expand Up @@ -151,6 +152,9 @@ public final class GiniBarButton {
buttonTitle = NSLocalizedStringPreferredFormat("ginicapture.imagepicker.openbutton",
comment: "Done")
icon = UIImageNamedPreferred(named: "barButton_done")
case .skip:
buttonTitle = NSLocalizedStringPreferredFormat("ginicapture.onboarding.skip",
comment: "Skip button")
}

let buttonTitleIsEmpty = buttonTitle == nil || buttonTitle!.isEmpty
Expand Down Expand Up @@ -188,6 +192,18 @@ public final class GiniBarButton {

return attributes
}

public func setContentHuggingPriority(_ priority: UILayoutPriority, for axis: NSLayoutConstraint.Axis) {
stackView.setContentHuggingPriority(priority, for: axis)
titleLabel.setContentHuggingPriority(priority, for: axis)
imageView.setContentHuggingPriority(priority, for: axis)
}

public func setContentCompressionResistancePriority(_ priority: UILayoutPriority, for axis: NSLayoutConstraint.Axis) {
stackView.setContentCompressionResistancePriority(priority, for: axis)
titleLabel.setContentCompressionResistancePriority(priority, for: axis)
imageView.setContentCompressionResistancePriority(priority, for: axis)
}
}

private extension GiniBarButton {
Expand Down
33 changes: 33 additions & 0 deletions Sources/GiniCaptureSDK/Core/Extensions/Date.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Date.swift
//
// Copyright © 2024 Gini GmbH. All rights reserved.
//

import Foundation

extension Date {
/**
Returns the current timestamp in milliseconds for the Berlin timezone.

This method calculates the current date and time, adjusts it to the Berlin timezone,
and then returns the timestamp in milliseconds since the Unix epoch (January 1, 1970).

- Returns: An `Int64` representing the current timestamp in milliseconds for the Berlin timezone.
*/
static func berlinTimestamp() -> Int64? {
let date = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = TimeZone(identifier: "Europe/Berlin")
let dateString = dateFormatter.string(from: date)

// Convert the formatted date string back to a Date object
guard let berlinDate = dateFormatter.date(from: dateString) else {
print("Failed to convert string to date")
return nil
}
// Convert the time interval to milliseconds and then to Int64
return Int64(berlinDate.timeIntervalSince1970 * 1000)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ extension UIViewController {
cancelActionTitle: cancelActionTitle,
confirmActionTitle: confirmActionTitle,
confirmAction: positiveAction)


GiniAnalyticsManager.track(event: .errorDialogShown,
screenName: .camera,
properties: [GiniAnalyticsProperty(key: .errorMessage, value: message)])
present(dialog, animated: true, completion: nil)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ import UIKit

- parameter message: The error type to be displayed.
*/
func displayError(
errorType: ErrorType,
animated: Bool
func displayError(errorType: ErrorType,
animated: Bool
)

/**
Expand Down Expand Up @@ -142,6 +141,11 @@ import UIKit
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
didShowAnalysis?()

let eventProperties = [GiniAnalyticsProperty(key: .documentType,
value: GiniAnalyticsMapper.documentTypeAnalytics(from: document.type))]
GiniAnalyticsManager.trackScreenShown(screenName: .analysis,
properties: eventProperties)
}

// MARK: Toggle animation
Expand Down
12 changes: 7 additions & 5 deletions Sources/GiniCaptureSDK/Core/Screens/Camera/Camera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,14 @@ fileprivate extension Camera {
completion(.success(videoDevice))
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { granted in
GiniAnalyticsManager.track(event: .cameraPermissionShown, screenName: .cameraPermissionView)
let permissionStatus: GiniCameraPermissionStatusAnalytics = granted ? .allowed : .notAllowed
let eventProperties = [GiniAnalyticsProperty(key: .permissionStatus, value: permissionStatus.rawValue)]
GiniAnalyticsManager.track(event: .cameraPermissionTapped,
screenName: .cameraPermissionView,
properties: eventProperties)
DispatchQueue.main.async {
if granted {
completion(.success(videoDevice))
} else {
completion(.failure(.notAuthorizedToUseDevice))
}
completion(granted ? .success(videoDevice) : .failure(.notAuthorizedToUseDevice))
}
}
case .denied, .restricted:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,26 @@ public final class CameraButtonsViewModel {
@objc func toggleFlash() {
isFlashOn = !isFlashOn
flashAction?(isFlashOn)

GiniAnalyticsManager.track(event: .flashTapped,
screenName: .camera,
properties: [GiniAnalyticsProperty(key: .flashActive, value: isFlashOn)])
}

@objc func importPressed() {
GiniAnalyticsManager.track(event: .importFilesTapped, screenName: .camera)
importAction?()
}

@objc func thumbnailPressed() {
GiniAnalyticsManager.track(event: .multiplePagesCapturedTapped,
screenName: .camera,
properties: [GiniAnalyticsProperty(key: .numberOfPagesScanned, value: images.count)])
imageStackAction?()
}

@objc func cancelPressed() {
GiniAnalyticsManager.track(event: .closeTapped, screenName: .camera)
cancelAction?()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
//
// Camera2ViewController+Actions.swift
//
// CameraViewController+Actions.swift
//
// Created by Krzysztof Kryniecki on 14/09/2022.
// Copyright © 2022 Gini GmbH. All rights reserved.
// Copyright © 2024 Gini GmbH. All rights reserved.
//

import UIKit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// Camera2ViewController+Extension.swift
//
// CameraViewController+Extension.swift
//
//
// Created by Krzysztof Kryniecki on 14/09/2022.
// Copyright © 2022 Gini GmbH. All rights reserved.
Expand All @@ -13,36 +13,40 @@ import UIKit
extension CameraViewController {

@objc func showImportFileSheet() {
let alertViewController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let alertViewControllerMessage: String = NSLocalizedStringPreferredFormat(
"ginicapture.camera.popupTitleImportPDForPhotos",
comment: "Info label")
let alertMessage = NSLocalizedStringPreferredFormat("ginicapture.camera.popupTitleImportPDForPhotos",
comment: "Info label")
let message = alertMessage.isEmpty ? nil : alertMessage
let alertViewController = UIAlertController(title: nil,
message: message,
preferredStyle: .actionSheet)

if giniConfiguration.fileImportSupportedTypes == .pdf_and_images {
alertViewController.addAction(UIAlertAction(title: NSLocalizedStringPreferredFormat(
"ginicapture.camera.popupOptionPhotos",
comment: "Photos action"),
style: .default) { [unowned self] _ in
let photosAlertActionTitle = NSLocalizedStringPreferredFormat("ginicapture.camera.popupOptionPhotos",
comment: "Photos action")
let photosAlertAction = UIAlertAction(title: photosAlertActionTitle,
style: .default) { [unowned self] _ in
GiniAnalyticsManager.track(event: .uploadPhotosTapped, screenName: .camera)
self.delegate?.camera(self, didSelect: .gallery)
})
}
alertViewController.addAction(photosAlertAction)
}

alertViewController.view.tintColor = .GiniCapture.accent1

alertViewController.addAction(UIAlertAction(title: NSLocalizedStringPreferredFormat(
"ginicapture.camera.popupOptionFiles",
comment: "files action"),
style: .default) { [unowned self] _ in
let filesAlertActionTitle = NSLocalizedStringPreferredFormat("ginicapture.camera.popupOptionFiles",
comment: "files action")
let filesAlertAction = UIAlertAction(title: filesAlertActionTitle,
style: .default) { [unowned self] _ in
GiniAnalyticsManager.track(event: .uploadDocumentsTapped, screenName: .camera)
self.delegate?.camera(self, didSelect: .explorer)
})
alertViewController.addAction(UIAlertAction(title: NSLocalizedStringPreferredFormat(
"ginicapture.camera.popupCancel",
comment: "cancel action"),
style: .cancel, handler: nil))
if alertViewControllerMessage.count > 0 {
alertViewController.message = alertViewControllerMessage
} else {
alertViewController.message = nil
}
alertViewController.addAction(filesAlertAction)

let cancelAlertActionTitle = NSLocalizedStringPreferredFormat("ginicapture.camera.popupCancel",
comment: "cancel action")
let cancelAlertAction = UIAlertAction(title: cancelAlertActionTitle,
style: .cancel, handler: nil)
alertViewController.addAction(cancelAlertAction)

alertViewController.popoverPresentationController?.sourceView = cameraPane.fileUploadButton
self.present(alertViewController, animated: true, completion: nil)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ final class CameraViewController: UIViewController {
private var validQRCodeProcessing: Bool = false

private var isValidIBANDetected: Bool = false
// Analytics
private var invalidQRCodeOverlayFirstAppearance: Bool = true
private var ibanOverlayFirstAppearance: Bool = true

weak var delegate: CameraViewControllerDelegate?

Expand Down Expand Up @@ -107,6 +110,10 @@ final class CameraViewController: UIViewController {
super.viewDidAppear(animated)
validQRCodeProcessing = false
delegate?.cameraDidAppear(self)

// this event should be sent every time when the user sees this screen, including
// when coming back from Help
GiniAnalyticsManager.trackScreenShown(screenName: .camera)
}

fileprivate func configureTitle() {
Expand Down Expand Up @@ -285,6 +292,7 @@ final class CameraViewController: UIViewController {
cameraPane.setupAuthorization(isHidden: false)
configureLeftButtons()
cameraButtonsViewModel.captureAction = { [weak self] in
self?.sendGiniAnalyticsEventCapture()
self?.cameraPane.toggleCaptureButtonActivation(state: false)
self?.cameraPreviewViewController.captureImage { [weak self] data, error in
guard let self = self else { return }
Expand Down Expand Up @@ -334,6 +342,16 @@ final class CameraViewController: UIViewController {
for: .touchUpInside)
}

private func sendGiniAnalyticsEventCapture() {
let eventProperties = [GiniAnalyticsProperty(key: .ibanDetectionLayerVisible,
value: !ibanDetectionOverLay.isHidden)]

GiniAnalyticsManager.track(event: .captureTapped,
screenName: .camera,
properties: eventProperties)

}

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)

Expand Down Expand Up @@ -485,16 +503,21 @@ final class CameraViewController: UIViewController {
}

private func showIBANOverlay(with IBANs: [String]) {

UIView.animate(withDuration: 0.3) {
self.ibanDetectionOverLay.isHidden = false
self.cameraPreviewViewController.changeCameraFrameColor(to: .GiniCapture.success2)
}

sendGiniAnalyticsEventIBANDetection()
ibanDetectionOverLay.configureOverlay(hidden: false)
ibanDetectionOverLay.setupView(with: IBANs)
}

private func sendGiniAnalyticsEventIBANDetection () {
guard ibanOverlayFirstAppearance else { return }
ibanOverlayFirstAppearance = false
}

private func hideIBANOverlay() {
guard !ibanDetectionOverLay.isHidden else { return }
UIView.animate(withDuration: 0.3) {
Expand Down Expand Up @@ -548,6 +571,10 @@ final class CameraViewController: UIViewController {
self.cameraPreviewViewController.changeQRFrameColor(to: .GiniCapture.success2)
}

// this event is sent once per SDK session since the message can be displayed often in the same session
GiniAnalyticsManager.track(event: .qr_code_scanned,
screenName: .camera,
properties: [GiniAnalyticsProperty(key: .qrCodeValid, value: true)])
qrCodeOverLay.configureQrCodeOverlay(withCorrectQrCode: true)
}

Expand All @@ -561,10 +588,19 @@ final class CameraViewController: UIViewController {
self.qrCodeOverLay.isHidden = false
self.cameraPreviewViewController.changeQRFrameColor(to: .GiniCapture.warning3)
}

sendGiniAnalyticsEventForInvalidQRCode()
qrCodeOverLay.configureQrCodeOverlay(withCorrectQrCode: false)
}

private func sendGiniAnalyticsEventForInvalidQRCode() {
guard invalidQRCodeOverlayFirstAppearance else { return }
// this event is sent once per SDK session since the message can be displayed often in the same session
GiniAnalyticsManager.track(event: .qr_code_scanned,
screenName: .camera,
properties: [GiniAnalyticsProperty(key: .qrCodeValid, value: false)])
invalidQRCodeOverlayFirstAppearance = false
}

private func resetQRCodeScanning(isValid: Bool) {
resetQRCodeTask = DispatchWorkItem(block: {
self.detectedQRCodeDocument = nil
Expand Down Expand Up @@ -633,14 +669,14 @@ extension CameraViewController: CameraLensSwitcherViewDelegate {
var device: AVCaptureDevice?

switch lens {
case .ultraWide:
if #available(iOS 13.0, *) {
device = AVCaptureDevice.default(.builtInUltraWideCamera, for: .video, position: .back)
}
case .wide:
device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
case .tele:
device = AVCaptureDevice.default(.builtInTelephotoCamera, for: .video, position: .back)
case .ultraWide:
if #available(iOS 13.0, *) {
device = AVCaptureDevice.default(.builtInUltraWideCamera, for: .video, position: .back)
}
case .wide:
device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
case .tele:
device = AVCaptureDevice.default(.builtInTelephotoCamera, for: .video, position: .back)
}

guard let device = device else { return }
Expand Down
Loading

0 comments on commit 211f58a

Please sign in to comment.