Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuration of showing country flags #1556

Merged
merged 6 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading