diff --git a/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift b/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift index 459daf7a..dfbba5d7 100644 --- a/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift +++ b/OmiseSDK/Sources/3DS/NetceteraThreeDSController.swift @@ -104,6 +104,7 @@ func apiKey(config: NetceteraConfig) throws -> String { key: decryptionKey ) + // swiftlint:disable:next non_optional_string_data_conversion if let apiKey = String(data: encrypted, encoding: .utf8) { return apiKey } else { diff --git a/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift b/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift index fab1dcbb..a252554d 100644 --- a/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift +++ b/OmiseSDK/Sources/Views/Screens/Credit Card Payment/CreditCardPaymentController.swift @@ -736,104 +736,28 @@ extension CreditCardPaymentController { updateSupplementaryUI() } - // swiftlint:disable:next function_body_length func configureAccessibility() { - formLabels.forEach { - $0.adjustsFontForContentSizeCategory = true - } - formFields.forEach { - $0.adjustsFontForContentSizeCategory = true - } - - let fields = formFields - + formLabels.forEach { $0.adjustsFontForContentSizeCategory = true } + formFields.forEach { $0.adjustsFontForContentSizeCategory = true } submitButton.titleLabel?.adjustsFontForContentSizeCategory = true - // swiftlint:disable:next function_body_length - func accessiblityElementAfter( - _ element: NSObjectProtocol?, - matchingPredicate predicate: (OmiseTextField) -> Bool, - direction: UIAccessibilityCustomRotor.Direction - ) -> NSObjectProtocol? { - guard let element = element else { - switch direction { - case .previous: - return fields.reversed().first(where: predicate)?.accessibilityElements?.last as? NSObjectProtocol - ?? fields.reversed().first(where: predicate) - case .next: - fallthrough - @unknown default: - return fields.first(where: predicate)?.accessibilityElements?.first as? NSObjectProtocol - ?? fields.first(where: predicate) - } - } - - let fieldOfElement = fields.first { field in - guard let accessibilityElements = field.accessibilityElements as? [NSObjectProtocol] else { - return element === field - } - - return accessibilityElements.contains { $0 === element } - } ?? cardNumberTextField! // swiftlint:disable:this force_unwrapping - - func filedAfter( - _ field: OmiseTextField, - matchingPredicate predicate: (OmiseTextField) -> Bool, - direction: UIAccessibilityCustomRotor.Direction - ) -> OmiseTextField? { - guard let indexOfField = fields.firstIndex(of: field) else { return nil } - switch direction { - case .previous: - return fields[fields.startIndex.. currentAccessibilityElements.startIndex { - return currentAccessibilityElements[currentAccessibilityElements.index(before: indexOfAccessibilityElement)] - } else { - return nextField?.accessibilityElements?.last as? NSObjectProtocol ?? nextField - } - case .next: - fallthrough - @unknown default: - if predicate(fieldOfElement) && indexOfAccessibilityElement < currentAccessibilityElements.endIndex - 1 { - return currentAccessibilityElements[currentAccessibilityElements.index(after: indexOfAccessibilityElement)] - } else { - return nextField?.accessibilityElements?.first as? NSObjectProtocol ?? nextField - } - } - } - + /* write unit tests for the code below */ accessibilityCustomRotors = [ - UIAccessibilityCustomRotor(name: "Fields") { (predicate) -> UIAccessibilityCustomRotorItemResult? in - return accessiblityElementAfter(predicate.currentItem.targetElement, - matchingPredicate: { _ in true }, - direction: predicate.searchDirection) + UIAccessibilityCustomRotor(name: "Fields") { [weak self] (predicate) -> UIAccessibilityCustomRotorItemResult? in + let fields = self?.formFields ?? [] + return self?.accessibilityElementAfter(predicate.currentItem.targetElement, + fields: fields, + matchingPredicate: { _ in true }, + direction: predicate.searchDirection) .map { UIAccessibilityCustomRotorItemResult(targetElement: $0, targetRange: nil) } }, - UIAccessibilityCustomRotor(name: "Invalid Data Fields") { (predicate) -> UIAccessibilityCustomRotorItemResult? in - return accessiblityElementAfter(predicate.currentItem.targetElement, - matchingPredicate: { !$0.isValid }, - direction: predicate.searchDirection) + + UIAccessibilityCustomRotor(name: "Invalid Data Fields") { [weak self] (predicate) -> UIAccessibilityCustomRotorItemResult? in + let fields = self?.formFields ?? [] + return self?.accessibilityElementAfter(predicate.currentItem.targetElement, + fields: fields, + matchingPredicate: { !$0.isValid }, + direction: predicate.searchDirection) .map { UIAccessibilityCustomRotorItemResult(targetElement: $0, targetRange: nil) } } ] @@ -1025,3 +949,101 @@ extension CreditCardPaymentController { view.endEditing(true) } } + +extension CreditCardPaymentController { + // Move out to the same level as configureAccessibility + func accessibilityElementAfter( + _ element: NSObjectProtocol?, + fields: [OmiseTextField], + matchingPredicate predicate: (OmiseTextField) -> Bool, + direction: UIAccessibilityCustomRotor.Direction + ) -> NSObjectProtocol? { + guard let element = element else { + return handleNoElement(direction, fields: fields, matchingPredicate: predicate) + } + return findAccessibilityElement(element, fields: fields, matchingPredicate: predicate, direction: direction) + } + + // This could be the new helper function handling cases when no element is provided + func handleNoElement( + _ direction: UIAccessibilityCustomRotor.Direction, + fields: [OmiseTextField], + matchingPredicate predicate: (OmiseTextField) -> Bool + ) -> NSObjectProtocol? { + + switch direction { + case .previous: + return fields.reversed().first(where: predicate)?.accessibilityElements?.last as? NSObjectProtocol + ?? fields.reversed().first(where: predicate) + case .next: + fallthrough + @unknown default: + return fields.first(where: predicate)?.accessibilityElements?.first as? NSObjectProtocol + ?? fields.first(where: predicate) + } + } + + // This could be another helper function finding an accessibility element + func findAccessibilityElement( + _ element: NSObjectProtocol, + fields: [OmiseTextField], + matchingPredicate predicate: (OmiseTextField) -> Bool, + direction: UIAccessibilityCustomRotor.Direction + ) -> NSObjectProtocol? { + let fieldOfElement = fields.first { field in + guard let accessibilityElements = field.accessibilityElements as? [NSObjectProtocol] else { + return element === field + } + + return accessibilityElements.contains { $0 === element } + } ?? cardNumberTextField! // swiftlint:disable:this force_unwrapping + + let nextField = filedAfter(fieldOfElement, fields: fields, matchingPredicate: predicate, direction: direction) + + guard let currentAccessibilityElements = (fieldOfElement.accessibilityElements as? [NSObjectProtocol]), + let indexOfAccessibilityElement = currentAccessibilityElements.firstIndex(where: { $0 === element }) else { + switch direction { + case .previous: + return nextField?.accessibilityElements?.last as? NSObjectProtocol ?? nextField + case .next: + fallthrough + @unknown default: + return nextField?.accessibilityElements?.first as? NSObjectProtocol ?? nextField + } + } + + switch direction { + case .previous: + if predicate(fieldOfElement) && indexOfAccessibilityElement > currentAccessibilityElements.startIndex { + return currentAccessibilityElements[currentAccessibilityElements.index(before: indexOfAccessibilityElement)] + } else { + return nextField?.accessibilityElements?.last as? NSObjectProtocol ?? nextField + } + case .next: + fallthrough + @unknown default: + if predicate(fieldOfElement) && indexOfAccessibilityElement < currentAccessibilityElements.endIndex - 1 { + return currentAccessibilityElements[currentAccessibilityElements.index(after: indexOfAccessibilityElement)] + } else { + return nextField?.accessibilityElements?.first as? NSObjectProtocol ?? nextField + } + } + } + + func filedAfter( + _ field: OmiseTextField, + fields: [OmiseTextField], + matchingPredicate predicate: (OmiseTextField) -> Bool, + direction: UIAccessibilityCustomRotor.Direction + ) -> OmiseTextField? { + guard let indexOfField = fields.firstIndex(of: field) else { return nil } + switch direction { + case .previous: + return fields[fields.startIndex..(_ object: T) throws -> String { encoder.outputFormatting = [.sortedKeys] let data = try encoder.encode(object) + // swiftlint:disable:next non_optional_string_data_conversion guard let result = String(data: data, encoding: .utf8) else { throw DataToStringCastError() } diff --git a/OmiseSDKTests/Helpers/String+JSON.swift b/OmiseSDKTests/Helpers/String+JSON.swift index 58b63215..0289cfbe 100644 --- a/OmiseSDKTests/Helpers/String+JSON.swift +++ b/OmiseSDKTests/Helpers/String+JSON.swift @@ -27,6 +27,7 @@ extension String { encoder.outputFormatting = [.sortedKeys] let data = try encoder.encode(encodable) + // swiftlint:disable:next non_optional_string_data_conversion guard let string = String(data: data, encoding: .utf8) else { throw StringFromDateError() } diff --git a/OmiseSDKTests/Source and Token JSON Codable Tests/SourceTests.swift b/OmiseSDKTests/Source and Token JSON Codable Tests/SourceTests.swift index d62c5a35..f932a385 100644 --- a/OmiseSDKTests/Source and Token JSON Codable Tests/SourceTests.swift +++ b/OmiseSDKTests/Source and Token JSON Codable Tests/SourceTests.swift @@ -11,6 +11,7 @@ class SourceTests: XCTestCase { /// Test Source.Payload's Codable protocol func validatePayloadCodable(_ payload: Payment) throws { let encodedPayload = try JSONEncoder().encode(payload) + // swiftlint:disable:next non_optional_string_data_conversion let encodedPayloadJson = String(data: encodedPayload, encoding: .utf8) ?? "" if payload.sourceType == .duitNowOBW { print(encodedPayloadJson) diff --git a/dev.xcodeproj/project.pbxproj b/dev.xcodeproj/project.pbxproj index 26933fbb..c77448f9 100644 --- a/dev.xcodeproj/project.pbxproj +++ b/dev.xcodeproj/project.pbxproj @@ -170,6 +170,7 @@ 75D13E1D2B86FF8C0073A831 /* CreditCardPaymentDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D13E1C2B86FF8C0073A831 /* CreditCardPaymentDelegate.swift */; }; 75D13E202B8703F80073A831 /* CreditCardPaymentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D13E1E2B8703F70073A831 /* CreditCardPaymentController.swift */; }; 75D13E212B8703F80073A831 /* CreditCardPaymentController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 75D13E1F2B8703F80073A831 /* CreditCardPaymentController.xib */; }; + 75D2C3EE2C19A80C006072D9 /* ThreeDS_SDK in Frameworks */ = {isa = PBXBuildFile; productRef = 75D2C3ED2C19A80C006072D9 /* ThreeDS_SDK */; }; 75D4E7062C05F50500ECCE72 /* OmiseErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D4E7052C05F50500ECCE72 /* OmiseErrorTests.swift */; }; 75DAD8902A0BB8D80098AF96 /* LocalConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DAD88F2A0BB8D80098AF96 /* LocalConfig.swift */; }; 75E0EB712B7A904100E3198A /* SourceFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75E0EB702B7A904100E3198A /* SourceFlowTests.swift */; }; @@ -511,6 +512,7 @@ buildActionMask = 2147483647; files = ( 75D13E0F2B8678530073A831 /* OmiseSwiftUIKit in Frameworks */, + 75D2C3EE2C19A80C006072D9 /* ThreeDS_SDK in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1294,6 +1296,7 @@ name = ExampleApp; packageProductDependencies = ( 75D13E0E2B8678530073A831 /* OmiseSwiftUIKit */, + 75D2C3ED2C19A80C006072D9 /* ThreeDS_SDK */, ); productName = ExampleApp; productReference = 22D4809D1D0EB29C00544CE1 /* ExampleApp.app */; @@ -2229,6 +2232,11 @@ isa = XCSwiftPackageProductDependency; productName = OmiseSwiftUIKit; }; + 75D2C3ED2C19A80C006072D9 /* ThreeDS_SDK */ = { + isa = XCSwiftPackageProductDependency; + package = 758A4E822BE38AC3005E7B5A /* XCRemoteSwiftPackageReference "SPM" */; + productName = ThreeDS_SDK; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 2259310F1CE3210700841B86 /* Project object */;