Skip to content
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
4 changes: 4 additions & 0 deletions Braintree.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
087831332F7B09CE003BB460 /* BraintreeCard.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7C889901B5F043B007A0E9C /* BraintreeCard.framework */; platformFilter = ios; };
087831392F7C1666003BB460 /* ValidationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 087831382F7C1666003BB460 /* ValidationResult.swift */; };
0878313B2F7C2839003BB460 /* CardFieldsContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0878313A2F7C2839003BB460 /* CardFieldsContainerView.swift */; };
6BE88A2459BC408288DC463C /* CardFieldsConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 802F8EBBED394EB59C531040 /* CardFieldsConstants.swift */; };
0878313D2F83FBDA003BB460 /* CVVCharacter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0878313C2F83FBDA003BB460 /* CVVCharacter.swift */; };
087CA76E2F0D72E80065D89B /* BTGraphQLHTTPError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 087CA76D2F0D72E80065D89B /* BTGraphQLHTTPError.swift */; };
08804D492F86D61E005ED3AC /* CVVFieldViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08804D472F86D61E005ED3AC /* CVVFieldViewModelTests.swift */; };
Expand Down Expand Up @@ -858,6 +859,7 @@
086D56D52F8568BB00120B47 /* CardNumberFieldValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardNumberFieldValidatorTests.swift; sourceTree = "<group>"; };
087831382F7C1666003BB460 /* ValidationResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidationResult.swift; sourceTree = "<group>"; };
0878313A2F7C2839003BB460 /* CardFieldsContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardFieldsContainerView.swift; sourceTree = "<group>"; };
802F8EBBED394EB59C531040 /* CardFieldsConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardFieldsConstants.swift; sourceTree = "<group>"; };
0878313C2F83FBDA003BB460 /* CVVCharacter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CVVCharacter.swift; sourceTree = "<group>"; };
087CA76D2F0D72E80065D89B /* BTGraphQLHTTPError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTGraphQLHTTPError.swift; sourceTree = "<group>"; };
08804D472F86D61E005ED3AC /* CVVFieldViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CVVFieldViewModelTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1648,6 +1650,7 @@
086D56CF2F8566B500120B47 /* CardBrand.swift */,
087831382F7C1666003BB460 /* ValidationResult.swift */,
0878313A2F7C2839003BB460 /* CardFieldsContainerView.swift */,
802F8EBBED394EB59C531040 /* CardFieldsConstants.swift */,
);
path = Shared;
sourceTree = "<group>";
Expand Down Expand Up @@ -3646,6 +3649,7 @@
files = (
0889DB142ED4C0350023AA64 /* PaymentButtonStyle.swift in Sources */,
0878313B2F7C2839003BB460 /* CardFieldsContainerView.swift in Sources */,
6BE88A2459BC408288DC463C /* CardFieldsConstants.swift in Sources */,
084135412ED0CBAD00884FE9 /* PaymentButtonView.swift in Sources */,
08F6DB282F6AEC67002FA6EE /* ExpirationDateFieldViewModel.swift in Sources */,
086D56D02F8566B500120B47 /* CardBrand.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ struct CVVFieldView: View {
// MARK: - Internal Properties

@ObservedObject var viewModel: CVVFieldViewModel
var containerWidth: CGFloat = CardFieldsConstants.defaultContainerWidth
var onAutoAdvance: (() -> Void)?

// MARK: - Private Properties
Expand All @@ -13,6 +14,11 @@ struct CVVFieldView: View {
@State private var showCVVHint: Bool = false
@State private var textFieldText: String = ""

private var popoverWidth: CGFloat {
let preferred = containerWidth - CardFieldsConstants.popoverWidthPadding
return min(max(CardFieldsConstants.popoverMinWidth, preferred), CardFieldsConstants.popoverMaxWidth)
}

// MARK: - View

var body: some View {
Expand Down Expand Up @@ -81,8 +87,8 @@ struct CVVFieldView: View {
.foregroundColor(Color(.secondaryLabel))
.fixedSize(horizontal: false, vertical: true)
}
.frame(width: 261)
.padding(16)
.frame(width: popoverWidth)
.padding(CardFieldsConstants.popoverPadding)
.accessibilityElement(children: .combine)
.accessibilityLabel("CVV help information")
.presentationCompactAdaptation(.popover)
Expand Down
37 changes: 31 additions & 6 deletions Sources/BraintreeUIComponents/CardFields/CardFields.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,23 @@ public struct CardFields: View {
// MARK: - Private Properties

@StateObject private var viewModel: CardFieldsViewModel
@State private var containerWidth: CGFloat = CardFieldsConstants.defaultContainerWidth
private var onValidityChange: ((Bool, @escaping () -> Void) -> Void)?

private var useHorizontalLayout: Bool {
containerWidth >= CardFieldsConstants.horizontalLayoutThreshold
}

private var expirationField: some View {
ExpirationDateFieldView(viewModel: viewModel.expirationDateViewModel) {
viewModel.cvvViewModel.isFocused = true
}
}

private var cvvField: some View {
CVVFieldView(viewModel: viewModel.cvvViewModel, containerWidth: containerWidth)
}

// MARK: - Initializer

/// Creates a `CardFields` form.
Expand Down Expand Up @@ -50,15 +65,25 @@ public struct CardFields: View {
}
)

HStack(spacing: 12) {
ExpirationDateFieldView(viewModel: viewModel.expirationDateViewModel) {
viewModel.cvvViewModel.isFocused = true
if useHorizontalLayout {
HStack(alignment: .top, spacing: CardFieldsConstants.fieldSpacing) {
expirationField
cvvField
}
} else {
VStack(spacing: CardFieldsConstants.fieldSpacing) {
expirationField
cvvField
}

CVVFieldView(viewModel: viewModel.cvvViewModel)
}
}
.padding()
.background(
GeometryReader { geo in
Color.clear
.onAppear { containerWidth = geo.size.width }
.onChange(of: geo.size.width) { _, newWidth in containerWidth = newWidth }
}
)
.onAppear {
onValidityChange?(viewModel.isFormValid, viewModel.tokenize)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import CoreGraphics

enum CardFieldsConstants {

// MARK: - Layout

static let fieldSpacing: CGFloat = 12
static let cornerRadius: CGFloat = 12
static let fieldHorizontalPadding: CGFloat = 16
static let fieldVerticalPadding: CGFloat = 14

// MARK: - Container Width

static let defaultContainerWidth: CGFloat = 280
static let horizontalLayoutThreshold: CGFloat = 260

// MARK: - CVV Popover

static let popoverMinWidth: CGFloat = 220
static let popoverMaxWidth: CGFloat = 300
static let popoverWidthPadding: CGFloat = 32
static let popoverPadding: CGFloat = 16
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ struct CardFieldsContainerView<Content: View>: View {

var body: some View {
VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 12) {
HStack(spacing: CardFieldsConstants.fieldSpacing) {
content()
}
.padding(.horizontal, 16)
.padding(.vertical, 14)
.padding(.horizontal, CardFieldsConstants.fieldHorizontalPadding)
.padding(.vertical, CardFieldsConstants.fieldVerticalPadding)
.background(Color(.systemBackground))
.clipShape(RoundedRectangle(cornerRadius: 12))
.clipShape(RoundedRectangle(cornerRadius: CardFieldsConstants.cornerRadius))
.overlay(
RoundedRectangle(cornerRadius: 12)
RoundedRectangle(cornerRadius: CardFieldsConstants.cornerRadius)
.stroke(borderColor, lineWidth: 1)
)

Expand Down
Loading