From 1b59c7cb351a6bf61842898fd9a4624a51ae44e4 Mon Sep 17 00:00:00 2001 From: Alex Guretzki Date: Mon, 18 Mar 2024 16:51:49 +0100 Subject: [PATCH 1/5] Allow configuration of showing country flags --- Adyen/UI/Form/Items/Address/AddressStyle.swift | 5 ++++- Adyen/UI/Form/Items/Address/FormAddressItem.swift | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Adyen/UI/Form/Items/Address/AddressStyle.swift b/Adyen/UI/Form/Items/Address/AddressStyle.swift index 19c33d781a..7ca25b9a06 100644 --- a/Adyen/UI/Form/Items/Address/AddressStyle.swift +++ b/Adyen/UI/Form/Items/Address/AddressStyle.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 Adyen N.V. +// Copyright (c) 2024 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -31,6 +31,9 @@ public struct AddressStyle: FormValueItemStyle { /// The color of form view item's separator line. public var separatorColor: UIColor? { textField.separatorColor } + /// Whether or not to show country flags in the country picker + public var showCountryFlags: Bool = true + /// Initializes the form address item configuration. /// - Parameters: /// - title: The section header style. diff --git a/Adyen/UI/Form/Items/Address/FormAddressItem.swift b/Adyen/UI/Form/Items/Address/FormAddressItem.swift index b44c0d248c..14d28c10cc 100644 --- a/Adyen/UI/Form/Items/Address/FormAddressItem.swift +++ b/Adyen/UI/Form/Items/Address/FormAddressItem.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 Adyen N.V. +// Copyright (c) 2024 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -108,7 +108,7 @@ public final class FormAddressItem: FormValueItem, return FormRegionPickerItem( preselectedRegion: defaultCountry, selectableRegions: countries, - shouldShowCountryFlags: true, + shouldShowCountryFlags: configuration.style.showCountryFlags, validationFailureMessage: localizedString( .countryFieldInvalid, configuration.localizationParameters From d9bfa9931c514cb81d6a4318939c88337a550db6 Mon Sep 17 00:00:00 2001 From: Alex Guretzki Date: Mon, 18 Mar 2024 17:15:52 +0100 Subject: [PATCH 2/5] Adding demo app settings for country flags --- Adyen/UI/Styles/FormComponentStyle.swift | 32 +++++++++++++------ .../CardComponentSettingsView.swift | 5 ++- .../ConfigurationViewModel.swift | 10 ++++-- Demo/Configuration.swift | 20 +++++++++--- 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/Adyen/UI/Styles/FormComponentStyle.swift b/Adyen/UI/Styles/FormComponentStyle.swift index cb573ea143..e05dd71d7b 100644 --- a/Adyen/UI/Styles/FormComponentStyle.swift +++ b/Adyen/UI/Styles/FormComponentStyle.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2022 Adyen N.V. +// Copyright (c) 2024 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -13,12 +13,22 @@ public struct FormComponentStyle: TintableStyle { public var backgroundColor = UIColor.Adyen.componentBackground /// The section header style. - public var sectionHeader = TextStyle(font: .preferredFont(forTextStyle: .headline), - color: UIColor.Adyen.componentLabel, - textAlignment: .natural) + public var sectionHeader = TextStyle( + font: .preferredFont(forTextStyle: .headline), + color: UIColor.Adyen.componentLabel, + textAlignment: .natural + ) { + didSet { + addressStyle.title = sectionHeader + } + } /// The text field style. - public var textField = FormTextItemStyle() + public var textField = FormTextItemStyle() { + didSet { + addressStyle.textField = textField + } + } /// The toggle style. public var toggle = FormToggleItemStyle() @@ -54,10 +64,7 @@ public struct FormComponentStyle: TintableStyle { ) /// The address style generated based on other field's value. - public var addressStyle: AddressStyle { - .init(title: sectionHeader, - textField: textField) - } + public var addressStyle: AddressStyle /// The error message indicator style. public var errorStyle = FormErrorItemStyle() @@ -101,6 +108,7 @@ public struct FormComponentStyle: TintableStyle { self.secondaryButtonItem = secondaryButton self.hintLabel = helper self.sectionHeader = sectionHeader + self.addressStyle = .init(title: sectionHeader, textField: textField) } /// Initializes the Form UI style. @@ -117,16 +125,20 @@ public struct FormComponentStyle: TintableStyle { self.toggle = toggle self.mainButtonItem = FormButtonItemStyle(button: mainButton) self.secondaryButtonItem = FormButtonItemStyle(button: secondaryButton) + self.addressStyle = .init(title: sectionHeader, textField: textField) } /// Initializes the form style with the default style and custom tint for all elements. /// - Parameter tintColor: The color for tinting buttons. textfields, icons and switches. public init(tintColor: UIColor) { + self.addressStyle = .init(title: sectionHeader, textField: textField) setTintColor(tintColor) } /// Initializes the form style with the default style. - public init() { /* public */ } + public init() { + self.addressStyle = .init(title: sectionHeader, textField: textField) + } private mutating func setTintColor(_ value: UIColor?) { guard let tintColor = value else { return } diff --git a/Demo/Common/Configuration/CardComponentSettingsView.swift b/Demo/Common/Configuration/CardComponentSettingsView.swift index 466fc4ef40..c769bb1709 100644 --- a/Demo/Common/Configuration/CardComponentSettingsView.swift +++ b/Demo/Common/Configuration/CardComponentSettingsView.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 Adyen N.V. +// Copyright (c) 2024 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -43,6 +43,9 @@ internal struct CardSettingsView: View { Text("Installment Amount") } } + Toggle(isOn: $viewModel.showCountryFlags) { + Text("Show country flags") + } } Section(header: Text("Input Modes")) { Picker("Billing Address mode", selection: $viewModel.addressMode) { diff --git a/Demo/Common/Configuration/ConfigurationViewModel.swift b/Demo/Common/Configuration/ConfigurationViewModel.swift index d14ff386fb..f7a290d590 100644 --- a/Demo/Common/Configuration/ConfigurationViewModel.swift +++ b/Demo/Common/Configuration/ConfigurationViewModel.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2023 Adyen N.V. +// Copyright (c) 2024 Adyen N.V. // // This file is open source and available under the MIT license. See the LICENSE file for more info. // @@ -32,6 +32,7 @@ internal final class ConfigurationViewModel: ObservableObject { @Published internal var cashAppPayEnabled: Bool = false @Published internal var installmentsEnabled: Bool = false @Published internal var showInstallmentAmount: Bool = false + @Published internal var showCountryFlags: Bool = true private let onDone: (DemoAppSettings) -> Void private let configuration: DemoAppSettings @@ -68,6 +69,7 @@ internal final class ConfigurationViewModel: ObservableObject { self.cashAppPayEnabled = configuration.dropInSettings.cashAppPayEnabled self.installmentsEnabled = configuration.cardSettings.enableInstallments self.showInstallmentAmount = configuration.cardSettings.showsInstallmentAmount + self.showCountryFlags = configuration.cardConfiguration.style.addressStyle.showCountryFlags } internal func doneTapped() { @@ -84,7 +86,8 @@ internal final class ConfigurationViewModel: ObservableObject { value: Int(value) ?? configuration.value, currencyCode: currencyCode, apiVersion: Int(apiVersion) ?? configuration.apiVersion, - merchantAccount: merchantAccount, cardSettings: CardSettings( + merchantAccount: merchantAccount, + cardSettings: CardSettings( showsHolderNameField: showsHolderNameField, showsStorePaymentMethodField: showsStorePaymentMethodField, showsStoredCardSecurityCodeField: showsStoredCardSecurityCodeField, @@ -93,7 +96,8 @@ internal final class ConfigurationViewModel: ObservableObject { socialSecurityNumberMode: socialSecurityNumberMode, koreanAuthenticationMode: koreanAuthenticationMode, enableInstallments: installmentsEnabled, - showsInstallmentAmount: showInstallmentAmount + showsInstallmentAmount: showInstallmentAmount, + showsCountryFlags: showCountryFlags ), dropInSettings: DropInSettings(allowDisablingStoredPaymentMethods: allowDisablingStoredPaymentMethods, allowsSkippingPaymentList: allowsSkippingPaymentList, diff --git a/Demo/Configuration.swift b/Demo/Configuration.swift index d5f6a997e8..b24af6ca31 100644 --- a/Demo/Configuration.swift +++ b/Demo/Configuration.swift @@ -101,6 +101,7 @@ internal struct CardSettings: Codable { internal var koreanAuthenticationMode: CardComponent.FieldVisibility = .auto internal var enableInstallments = false internal var showsInstallmentAmount = false + internal var showsCountryFlags = true internal enum AddressFormType: String, Codable, CaseIterable { case lookup @@ -176,7 +177,8 @@ internal struct DemoAppSettings: Codable { socialSecurityNumberMode: .auto, koreanAuthenticationMode: .auto, enableInstallments: false, - showsInstallmentAmount: false) + showsInstallmentAmount: false, + showsCountryFlags: true) internal static let defaultDropInSettings = DropInSettings(allowDisablingStoredPaymentMethods: false, allowsSkippingPaymentList: false, @@ -213,8 +215,12 @@ internal struct DemoAppSettings: Codable { var billingAddressConfig = BillingAddressConfiguration() billingAddressConfig.mode = cardComponentAddressFormType(from: cardSettings.addressMode) + + var style = FormComponentStyle() + style.addressStyle.showCountryFlags = cardSettings.showsCountryFlags - return .init(showsHolderNameField: cardSettings.showsHolderNameField, + return .init(style: style, + showsHolderNameField: cardSettings.showsHolderNameField, showsStorePaymentMethodField: cardSettings.showsStorePaymentMethodField, showsSecurityCodeField: cardSettings.showsSecurityCodeField, koreanAuthenticationMode: cardSettings.koreanAuthenticationMode, @@ -243,8 +249,14 @@ internal struct DemoAppSettings: Codable { } internal var dropInConfiguration: DropInComponent.Configuration { - let dropInConfig = DropInComponent.Configuration(allowsSkippingPaymentList: dropInSettings.allowsSkippingPaymentList, - allowPreselectedPaymentView: dropInSettings.allowPreselectedPaymentView) + var style = DropInComponent.Style() + style.formComponent.addressStyle.showCountryFlags = cardSettings.showsCountryFlags + + let dropInConfig = DropInComponent.Configuration( + style: style, + allowsSkippingPaymentList: dropInSettings.allowsSkippingPaymentList, + allowPreselectedPaymentView: dropInSettings.allowPreselectedPaymentView + ) dropInConfig.paymentMethodsList.allowDisablingStoredPaymentMethods = dropInSettings.allowDisablingStoredPaymentMethods if dropInSettings.cashAppPayEnabled { From f0bf8af87ad54b2326922986177bb0fd8638ec81 Mon Sep 17 00:00:00 2001 From: Alex Guretzki Date: Tue, 19 Mar 2024 10:56:40 +0100 Subject: [PATCH 3/5] Adding tests to ensure behavior --- .../Address/FormAddressPickerItemTests.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Tests/Adyen Tests/UI/Form/FormItems/Address/FormAddressPickerItemTests.swift b/Tests/Adyen Tests/UI/Form/FormItems/Address/FormAddressPickerItemTests.swift index d9f7aec63c..f7d6e78fca 100644 --- a/Tests/Adyen Tests/UI/Form/FormItems/Address/FormAddressPickerItemTests.swift +++ b/Tests/Adyen Tests/UI/Form/FormItems/Address/FormAddressPickerItemTests.swift @@ -9,6 +9,22 @@ import XCTest class FormAddressPickerItemTests: XCTestCase { + func test_addressStyle_reflectsComponentStyleChanges() { + + var style = FormComponentStyle() + XCTAssertEqual(style.addressStyle.title, style.sectionHeader) + XCTAssertEqual(style.addressStyle.textField.title, style.textField.title) + + let expectedSectonHeaderStyle = TextStyle(font: .systemFont(ofSize: 200), color: .yellow) + let expectedTextFieldStyle = FormTextItemStyle(tintColor: .green) + + style.sectionHeader = expectedSectonHeaderStyle + style.textField = expectedTextFieldStyle + + XCTAssertEqual(style.addressStyle.title, expectedSectonHeaderStyle) + XCTAssertEqual(style.addressStyle.textField.title, expectedTextFieldStyle.title) + } + func testEmptyPrefillAddress() { let addressLookupItem = FormAddressPickerItem( From d37e6f6448775db646c491ea77b12ae7e9c7345b Mon Sep 17 00:00:00 2001 From: Alex Guretzki Date: Tue, 19 Mar 2024 11:51:17 +0100 Subject: [PATCH 4/5] Add flag visibility tests --- .../AddressInputFormViewControllerTests.swift | 72 +++++++++++++++---- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/Tests/Adyen Tests/UI/View Controllers/AddressInputFormViewControllerTests.swift b/Tests/Adyen Tests/UI/View Controllers/AddressInputFormViewControllerTests.swift index cf94c4dc46..4fe70f7471 100644 --- a/Tests/Adyen Tests/UI/View Controllers/AddressInputFormViewControllerTests.swift +++ b/Tests/Adyen Tests/UI/View Controllers/AddressInputFormViewControllerTests.swift @@ -207,7 +207,6 @@ class AddressInputFormViewControllerTests: XCTestCase { let viewController = AddressInputFormViewController( viewModel: self.viewModel( initialCountry: "CA", - prefillAddress: nil, searchHandler: { currentInput in XCTAssertEqual(currentInput, .init(country: "CA")) searchExpectation.fulfill() @@ -229,9 +228,7 @@ class AddressInputFormViewControllerTests: XCTestCase { let viewController = AddressInputFormViewController( viewModel: self.viewModel( - initialCountry: "NL", - prefillAddress: nil, - searchHandler: nil + initialCountry: "NL" ) ) @@ -248,8 +245,7 @@ class AddressInputFormViewControllerTests: XCTestCase { let viewController = AddressInputFormViewController( viewModel: self.viewModel( initialCountry: "NL", - prefillAddress: .init(country: "US"), - searchHandler: nil + prefillAddress: .init(country: "US") ) ) @@ -276,8 +272,7 @@ class AddressInputFormViewControllerTests: XCTestCase { let viewController = AddressInputFormViewController( viewModel: self.viewModel( initialCountry: "NL", - prefillAddress: .init(country: "NL", street: "Singel"), - searchHandler: nil + prefillAddress: .init(country: "NL", street: "Singel") ) ) @@ -294,8 +289,7 @@ class AddressInputFormViewControllerTests: XCTestCase { let viewController = AddressInputFormViewController( viewModel: self.viewModel( initialCountry: "NL", - prefillAddress: .init(country: "NL", street: "Singel"), - searchHandler: nil + prefillAddress: .init(country: "NL", street: "Singel") ) ) @@ -307,19 +301,69 @@ class AddressInputFormViewControllerTests: XCTestCase { "" ) } + + func test_countryFlags_showByDefault() throws { + + var style = FormComponentStyle(tintColor: .blue) + + let viewController = AddressInputFormViewController( + viewModel: self.viewModel(style: style) + ) + + let pickerSearchViewController = try presentCountryPicker(for: viewController) + let firstListItem = try firstListItem(from: pickerSearchViewController) + XCTAssertNil(firstListItem.icon) + XCTAssertEqual(firstListItem.title, "Afghanistan") + XCTAssertEqual(firstListItem.subtitle, "🇦🇫 AF") + } + + func test_countryFlags_dontNotShowIfConfigured() throws { + + var style = FormComponentStyle(tintColor: .blue) + style.addressStyle.showCountryFlags = false + + let viewController = AddressInputFormViewController( + viewModel: self.viewModel(style: style) + ) + + let pickerSearchViewController = try presentCountryPicker(for: viewController) + let firstListItem = try firstListItem(from: pickerSearchViewController) + XCTAssertNil(firstListItem.icon) + XCTAssertEqual(firstListItem.title, "Afghanistan") + XCTAssertEqual(firstListItem.subtitle, "AF") + } } private extension AddressInputFormViewControllerTests { + func presentCountryPicker( + for viewController: AddressInputFormViewController + ) throws -> FormPickerSearchViewController { + setupRootViewController(viewController) + let view: UIView = viewController.view + let countryItemView: FormPickerItemView = try XCTUnwrap(view.findView(with: "AddressInputFormViewController.address.country")) + countryItemView.item.selectionHandler() + return try waitUntilTopPresenter(isOfType: FormPickerSearchViewController.self) + } + + func firstListItem(from pickerSearchViewController: FormPickerSearchViewController) throws -> ListItem { + let searchViewController = try XCTUnwrap(pickerSearchViewController.viewControllers.first as? SearchViewController) + let resultsList = searchViewController.resultsListViewController + wait { resultsList.viewIfLoaded?.window != nil } + let firstCell = try XCTUnwrap(resultsList.tableView.visibleCells.first as? ListCell) + return try XCTUnwrap(firstCell.item) + } + func viewModel( - initialCountry: String, - prefillAddress: PostalAddress?, - searchHandler: AddressInputFormViewController.ShowSearchHandler? + initialCountry: String = "NL", + prefillAddress: PostalAddress? = nil, + style: FormComponentStyle = .init(), + searchHandler: AddressInputFormViewController.ShowSearchHandler? = nil ) -> AddressInputFormViewController.ViewModel { .init( for: .billing, - style: .init(), + style: style, localizationParameters: nil, initialCountry: initialCountry, prefillAddress: prefillAddress, From 948da340512a9ccd99fa8f7f32a2c0910c3f2807 Mon Sep 17 00:00:00 2001 From: Alex Guretzki Date: Tue, 19 Mar 2024 14:22:22 +0100 Subject: [PATCH 5/5] Adding example to Customization.md --- Adyen.docc/Customization.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Adyen.docc/Customization.md b/Adyen.docc/Customization.md index 0be8d9d4c7..5f17e64264 100644 --- a/Adyen.docc/Customization.md +++ b/Adyen.docc/Customization.md @@ -14,6 +14,7 @@ For example, to change the section header titles and form field titles in the Dr style.formComponent.textField.title.color = .red style.formComponent.mainButtonItem.button.backgroundColor = .black style.formComponent.mainButtonItem.button.title.color = .white + style.formComponent.addressStyle.showCountryFlags = false // Hiding flags from the country picker let configuration = DropInComponent.Configuration(style: style) let component = DropInComponent(paymentMethods: paymentMethods, @@ -30,6 +31,7 @@ For example, to change the section header titles and form field titles in the Dr style.textField.title.color = .white style.textField.text.color = .white style.switch.title.color = .white + style.addressStyle.showCountryFlags = false // Hiding flags from the country picker let config = CardComponent.Configuration(style: style) let component = CardComponent(paymentMethod: paymentMethod,