Skip to content

Commit

Permalink
Add FXIOS-8383 [V124] Add a Custom Autofill Accessory View Supporting…
Browse files Browse the repository at this point in the history
… Credit Cards and Addresses (#18584)

* Add a Custom Autofill Accessory View Supporting Credit Cards and Addresses

* pr comment

* pr comment

* Updating credit card location

* pr comment

* pr comment

* pr comment

* lint fixes

* added padding

* swift lint errors

---------

Co-authored-by: Nishant Bhasin <[email protected]>
  • Loading branch information
jnrahme and nbhasin2 authored Feb 7, 2024
1 parent f59327a commit 0fbe36c
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 100 deletions.
4 changes: 4 additions & 0 deletions firefox-ios/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,7 @@
B15058812AA0A878008B7382 /* OpeningScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B15058802AA0A878008B7382 /* OpeningScreenTests.swift */; };
B1664E9E2B163B7A005D4C71 /* CreditCardsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1664E9D2B163B7A005D4C71 /* CreditCardsTests.swift */; };
B26ADF852B339ED000C6E127 /* AddressAutofillSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = B26ADF842B339ED000C6E127 /* AddressAutofillSetting.swift */; };
B2981F8A2B71AD7A00132C1B /* AutofillAccessoryViewButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2981F892B71AD7A00132C1B /* AutofillAccessoryViewButtonItem.swift */; };
B2999FED2B044A5900F0FEC1 /* UnencryptedCreditCardFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2999FEC2B044A5900F0FEC1 /* UnencryptedCreditCardFields.swift */; };
B2999FEF2B044B4E00F0FEC1 /* RustAutofillEncryptionKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2999FEE2B044B4E00F0FEC1 /* RustAutofillEncryptionKeys.swift */; };
B2999FF12B194A5800F0FEC1 /* CreditCardPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2999FF02B194A5800F0FEC1 /* CreditCardPayload.swift */; };
Expand Down Expand Up @@ -6207,6 +6208,7 @@
B26ADF842B339ED000C6E127 /* AddressAutofillSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressAutofillSetting.swift; sourceTree = "<group>"; };
B29049688244A8F79950DF35 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/Shared.strings; sourceTree = "<group>"; };
B2944B3EAFB1DA22E7F28520 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Shared.strings; sourceTree = "<group>"; };
B2981F892B71AD7A00132C1B /* AutofillAccessoryViewButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillAccessoryViewButtonItem.swift; sourceTree = "<group>"; };
B2999FEC2B044A5900F0FEC1 /* UnencryptedCreditCardFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnencryptedCreditCardFields.swift; sourceTree = "<group>"; };
B2999FEE2B044B4E00F0FEC1 /* RustAutofillEncryptionKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustAutofillEncryptionKeys.swift; sourceTree = "<group>"; };
B2999FF02B194A5800F0FEC1 /* CreditCardPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditCardPayload.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -8716,6 +8718,7 @@
43D00490296FC44B00CB0F31 /* Autofill */ = {
isa = PBXGroup;
children = (
B2981F892B71AD7A00132C1B /* AutofillAccessoryViewButtonItem.swift */,
B2FEA6892B460CEC0058E616 /* Address */,
43D00491296FC46E00CB0F31 /* CreditCard */,
);
Expand Down Expand Up @@ -13593,6 +13596,7 @@
EBB8950C21939E4100EB91A0 /* FirefoxTabContentBlocker.swift in Sources */,
21618A632A422A3900A5189E /* ThemeMiddleware.swift in Sources */,
219A0FDB2ACCCFFC009A6D1A /* InactiveTabsSectionManager.swift in Sources */,
B2981F8A2B71AD7A00132C1B /* AutofillAccessoryViewButtonItem.swift in Sources */,
211F00AC27F4D918001D9189 /* HistoryPanel+Search.swift in Sources */,
96EB6C3827D821B800A9D159 /* HistoryPanelViewModel.swift in Sources */,
AB52ED3B2A0E8873001067F5 /* UserConversionMetrics.swift in Sources */,
Expand Down
179 changes: 79 additions & 100 deletions firefox-ios/Client/AccessoryViewProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,61 +7,56 @@ import Common
import Shared

enum AccessoryType {
case standard, creditCard
case standard, creditCard, address
}

class AccessoryViewProvider: UIView, Themeable {
// MARK: - Constants
private struct UX {
static let toolbarHeight: CGFloat = 50
static let cornerRadius: CGFloat = 4
static let cardImageViewSize: CGFloat = 24
static let fixedSpacerWidth: CGFloat = 10
static let fixedSpacerHeight: CGFloat = 30
static let fixedLeadingSpacerWidth: CGFloat = 2
static let fixedTrailingSpacerWidth: CGFloat = 3
static let cardButtonStackViewSpacing: CGFloat = 2
}

// MARK: - Properties
var themeManager: ThemeManager
var themeObserver: NSObjectProtocol?
var notificationCenter: NotificationProtocol
private var showCreditCard = false
private var currentAccessoryView: AutofillAccessoryViewButtonItem?

// stubs - these closures will be given as selectors in a future task
// Stub closures - these closures will be given as selectors in a future task
var previousClosure: (() -> Void)?
var nextClosure: (() -> Void)?
var doneClosure: (() -> Void)?
var savedCardsClosure: (() -> Void)?
var savedAddressesClosure: (() -> Void)?

private var toolbar: UIToolbar = .build { toolbar in
toolbar.sizeToFit()
// MARK: - UI Elements
private let toolbar: UIToolbar = .build {
$0.sizeToFit()
}

private lazy var previousButton: UIBarButtonItem = {
let button = UIBarButtonItem(image: UIImage(named: StandardImageIdentifiers.Large.chevronUp),
style: .plain,
target: self,
action: #selector(tappedPreviousButton))

return button
.init(image: UIImage(named: StandardImageIdentifiers.Large.chevronUp),
style: .plain,
target: self,
action: #selector(tappedPreviousButton))
}()

private lazy var nextButton: UIBarButtonItem = {
let button = UIBarButtonItem(image: UIImage(named: StandardImageIdentifiers.Large.chevronDown),
style: .plain,
target: self,
action: #selector(tappedNextButton))

return button
.init(image: UIImage(named: StandardImageIdentifiers.Large.chevronDown),
style: .plain,
target: self,
action: #selector(tappedNextButton))
}()

private lazy var doneButton: UIBarButtonItem = {
let button = UIBarButtonItem(title: .CreditCard.Settings.Done,
style: .done,
target: self,
action: #selector(tappedDoneButton))

return button
.init(title: .CreditCard.Settings.Done,
style: .done,
target: self,
action: #selector(tappedDoneButton))
}()

private lazy var fixedSpacer: UIBarButtonItem = {
Expand All @@ -71,47 +66,36 @@ class AccessoryViewProvider: UIView, Themeable {
fixedSpacer.width = CGFloat(UX.fixedSpacerWidth)
return fixedSpacer
}()
private let flexibleSpacer = UIBarButtonItem(systemItem: .flexibleSpace)

private let flexibleSpacer = UIBarButtonItem(systemItem: .flexibleSpace)
private let leadingFixedSpacer: UIView = .build()
private let trailingFixedSpacer: UIView = .build()

private lazy var cardImageView: UIImageView = .build { imageView in
imageView.image = UIImage(named: StandardImageIdentifiers.Large.creditCard)?.withRenderingMode(.alwaysTemplate)
imageView.contentMode = .scaleAspectFit
imageView.accessibilityElementsHidden = true

NSLayoutConstraint.activate([
imageView.widthAnchor.constraint(equalToConstant: UX.cardImageViewSize),
imageView.heightAnchor.constraint(equalToConstant: UX.cardImageViewSize)
])
}

private lazy var useCardTextLabel: UILabel = .build { label in
label.font = DefaultDynamicFontHelper.preferredFont(withTextStyle: .title3, size: 16, weight: .medium)
label.text = .CreditCard.Settings.UseSavedCardFromKeyboard
label.numberOfLines = 0
label.accessibilityTraits = .button
}

private lazy var cardButtonStackView: UIStackView = .build { [weak self] stackView in
guard let self = self else { return }

let stackViewTapped = UITapGestureRecognizer(target: self, action: #selector(self.tappedCardButton))

stackView.isUserInteractionEnabled = true
stackView.addArrangedSubview(self.leadingFixedSpacer)
stackView.addArrangedSubview(self.cardImageView)
stackView.addArrangedSubview(self.useCardTextLabel)
stackView.addArrangedSubview(self.trailingFixedSpacer)
stackView.spacing = UX.cardButtonStackViewSpacing
stackView.distribution = .equalCentering
stackView.layer.cornerRadius = UX.cornerRadius
stackView.addGestureRecognizer(stackViewTapped)
}
private lazy var creditCardAutofillView: AutofillAccessoryViewButtonItem = {
let accessoryView = AutofillAccessoryViewButtonItem(
image: UIImage(named: StandardImageIdentifiers.Large.creditCard),
labelText: .CreditCard.Settings.UseSavedCardFromKeyboard,
tappedAction: { [weak self] in
self?.tappedCreditCardButton()
})
accessoryView.accessibilityTraits = .button
accessoryView.accessibilityLabel = .CreditCard.Settings.UseSavedCardFromKeyboard
return accessoryView
}()

// MARK: Lifecycle
private lazy var addressAutofillView: AutofillAccessoryViewButtonItem = {
let accessoryView = AutofillAccessoryViewButtonItem(
image: UIImage(named: StandardImageIdentifiers.Large.location),
labelText: .Addresses.Settings.UseSavedAddressFromKeyboard,
tappedAction: { [weak self] in
self?.tappedAddressCardButton()
})
accessoryView.accessibilityTraits = .button
accessoryView.accessibilityLabel = .Addresses.Settings.UseSavedAddressFromKeyboard
return accessoryView
}()

// MARK: - Initialization
init(themeManager: ThemeManager = AppContainer.shared.resolve(),
notificationCenter: NotificationCenter = NotificationCenter.default) {
self.themeManager = themeManager
Expand All @@ -134,19 +118,21 @@ class AccessoryViewProvider: UIView, Themeable {
// Reset showing of credit card when dismissing the view
// This is required otherwise it will always show credit card view
// even if the input isn't of type credit card
showCreditCard = false
currentAccessoryView = nil
setupLayout()
}

// MARK: Layout and Theme
// MARK: - Theme and Layout

func reloadViewFor(_ accessoryType: AccessoryType) {
switch accessoryType {
case .standard:
showCreditCard = false
currentAccessoryView = nil
case .creditCard:
showCreditCard = true
currentAccessoryView = creditCardAutofillView
sendCreditCardAutofillPromptShownTelemetry()
case .address:
currentAccessoryView = addressAutofillView
}

setNeedsLayout()
Expand All @@ -163,54 +149,42 @@ class AccessoryViewProvider: UIView, Themeable {
}

private func setupLayout() {
translatesAutoresizingMaskIntoConstraints = false
setupSpacer(leadingFixedSpacer, width: UX.fixedLeadingSpacerWidth)
setupSpacer(trailingFixedSpacer, width: UX.fixedTrailingSpacerWidth)

if showCreditCard {
let cardStackViewForBarButton = UIBarButtonItem(customView: cardButtonStackView)
cardStackViewForBarButton.accessibilityTraits = .button
cardStackViewForBarButton.accessibilityLabel = .CreditCard.Settings.UseSavedCardFromKeyboard
toolbar.items = [
previousButton,
nextButton,
fixedSpacer,
cardStackViewForBarButton,
flexibleSpacer,
doneButton
]
toolbar.accessibilityElements = [
previousButton,
nextButton,
cardStackViewForBarButton,
doneButton
]
} else {
toolbar.items = [
previousButton,
nextButton,
flexibleSpacer,
doneButton
]
var toolbarItems: [UIBarButtonItem] = [
flexibleSpacer,
previousButton,
nextButton,
fixedSpacer,
doneButton
]

if let accessoryView = currentAccessoryView {
toolbarItems.insert(contentsOf: [ accessoryView], at: 0)
}

toolbar.setItems(toolbarItems, animated: false)
addSubview(toolbar)

NSLayoutConstraint.activate([
toolbar.widthAnchor.constraint(equalTo: super.widthAnchor),
toolbar.heightAnchor.constraint(equalToConstant: UX.toolbarHeight)
toolbar.leadingAnchor.constraint(equalTo: leadingAnchor),
toolbar.trailingAnchor.constraint(equalTo: trailingAnchor),
toolbar.topAnchor.constraint(equalTo: topAnchor),
toolbar.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}

func applyTheme() {
let theme = themeManager.currentTheme

backgroundColor = theme.colors.layer5
previousButton.tintColor = theme.colors.iconAccentBlue
nextButton.tintColor = theme.colors.iconAccentBlue
doneButton.tintColor = theme.colors.iconAccentBlue
cardImageView.tintColor = theme.colors.iconPrimary
cardButtonStackView.backgroundColor = theme.colors.layer5Hover
[previousButton, nextButton, doneButton].forEach { $0.tintColor = theme.colors.iconAccentBlue }

[creditCardAutofillView, addressAutofillView].forEach {
$0.accessoryImageViewTintColor = theme.colors.iconPrimary
$0.backgroundColor = theme.colors.layer5Hover
}
}

// MARK: - Actions
Expand All @@ -231,11 +205,16 @@ class AccessoryViewProvider: UIView, Themeable {
}

@objc
private func tappedCardButton() {
private func tappedCreditCardButton() {
savedCardsClosure?()
}

// MARK: Telemetry
@objc
private func tappedAddressCardButton() {
savedAddressesClosure?()
}

// MARK: - Telemetry
fileprivate func sendCreditCardAutofillPromptShownTelemetry() {
TelemetryWrapper.recordEvent(category: .action,
method: .view,
Expand Down
Loading

0 comments on commit 0fbe36c

Please sign in to comment.