Skip to content

Commit

Permalink
Allow configuration of showing country flags (#1556)
Browse files Browse the repository at this point in the history
## Summary
<new>
In the country/region picker for the payment form, you can now choose if
the images of flags are shown.

**Components**
```
let cardConfiguration = CardComponent.Configuration()
cardConfiguration.style.addressStyle.showCountryFlags = true/false
let cardComponent = CardComponent(paymentMethod: paymentMethod,
                                  context: context,
                                  configuration: cardConfiguration)
```

**Drop-In**
```
var style = DropInComponent.Style()
style.formComponent.addressStyle.showCountryFlags = cardSettings.showsCountryFlags
let dropInConfiguration = DropInComponent.Configuration(style: style)
let dropInComponent = DropInComponent(paymentMethods: paymentMethods,
                                      context: context,
                                      configuration: dropInConfiguration)
```
</new>

### Also
- Adds a configuration to show/hide the flags in the Demo app
- Adds tests for flag visibility

| List (showing flags) | Settings (enabled) |
| --------- | --------- |
| <img
src="https://github.com/Adyen/adyen-ios/assets/4838877/a7c3c398-097f-4041-adcc-838a73da4331"
width="200px"> | <img
src="https://github.com/Adyen/adyen-ios/assets/4838877/665e09e0-7366-429f-b583-5fcc39b8098a"
width="200px"> |

| List (no flags) | Settings (disabled) |
| --------- | --------- |
| <img
src="https://github.com/Adyen/adyen-ios/assets/4838877/2098ee8a-e817-4b13-b9b0-198d67cd44d0"
width="200px"> | <img
src="https://github.com/Adyen/adyen-ios/assets/4838877/57f0fb6c-85a8-4263-b2ec-a960f95a0c76"
width="200px"> |
  • Loading branch information
goergisn authored Mar 20, 2024
2 parents fa840e8 + ba1a232 commit 5f425a5
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 35 deletions.
2 changes: 2 additions & 0 deletions Adyen.docc/Customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion Adyen/UI/Form/Items/Address/AddressStyle.swift
Original file line number Diff line number Diff line change
@@ -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.
//
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions Adyen/UI/Form/Items/Address/FormAddressItem.swift
Original file line number Diff line number Diff line change
@@ -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.
//
Expand Down Expand Up @@ -108,7 +108,7 @@ public final class FormAddressItem: FormValueItem<PostalAddress, AddressStyle>,
return FormRegionPickerItem(
preselectedRegion: defaultCountry,
selectableRegions: countries,
shouldShowCountryFlags: true,
shouldShowCountryFlags: configuration.style.showCountryFlags,
validationFailureMessage: localizedString(
.countryFieldInvalid,
configuration.localizationParameters
Expand Down
32 changes: 22 additions & 10 deletions Adyen/UI/Styles/FormComponentStyle.swift
Original file line number Diff line number Diff line change
@@ -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.
//
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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.
Expand All @@ -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 }
Expand Down
5 changes: 4 additions & 1 deletion Demo/Common/Configuration/CardComponentSettingsView.swift
Original file line number Diff line number Diff line change
@@ -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.
//
Expand Down Expand Up @@ -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) {
Expand Down
10 changes: 7 additions & 3 deletions Demo/Common/Configuration/ConfigurationViewModel.swift
Original file line number Diff line number Diff line change
@@ -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.
//
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand All @@ -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,
Expand All @@ -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,
Expand Down
20 changes: 16 additions & 4 deletions Demo/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -229,9 +228,7 @@ class AddressInputFormViewControllerTests: XCTestCase {

let viewController = AddressInputFormViewController(
viewModel: self.viewModel(
initialCountry: "NL",
prefillAddress: nil,
searchHandler: nil
initialCountry: "NL"
)
)

Expand All @@ -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")
)
)

Expand All @@ -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")
)
)

Expand All @@ -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")
)
)

Expand All @@ -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,
Expand Down

0 comments on commit 5f425a5

Please sign in to comment.