Skip to content

Commit e75248d

Browse files
authored
Merge pull request #8299 from woocommerce/issue/8082-add-specific-alertprovider-for-built-in-reader-connections
[Mobile Payments] Specific alert provider for built-in reader connections
2 parents c9ee001 + dce1972 commit e75248d

File tree

16 files changed

+496
-39
lines changed

16 files changed

+496
-39
lines changed

WooCommerce/Classes/Extensions/UIImage+Woo.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,18 @@ extension UIImage {
498498
return UIImage(named: "card-reader-scanning")!
499499
}
500500

501+
static var tempBuiltInReaderCheck: UIImage {
502+
return UIImage(named: "temp-woo-tap-on-mobile-check")!
503+
}
504+
505+
static var tempBuiltInReaderPrepare: UIImage {
506+
return UIImage(named: "temp-woo-tap-on-mobile-prepare")!
507+
}
508+
509+
static var tempBuiltInReaderPayment: UIImage {
510+
return UIImage(named: "temp-woo-tap-on-mobile")!
511+
}
512+
501513
/// Found Card Reader
502514
///
503515
static var cardReaderFound: UIImage {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import UIKit
2+
3+
/// Modal presented when we are connecting to a reader
4+
///
5+
final class CardPresentModalBuiltInConnectingToReader: CardPresentPaymentsModalViewModel {
6+
let textMode: PaymentsModalTextMode = .reducedBottomInfo
7+
let actionsMode: PaymentsModalActionsMode = .none
8+
9+
let topTitle: String = Localization.title
10+
11+
var topSubtitle: String?
12+
13+
let image: UIImage = .tempBuiltInReaderCheck
14+
15+
let primaryButtonTitle: String? = nil
16+
17+
let secondaryButtonTitle: String? = nil
18+
19+
let auxiliaryButtonTitle: String? = nil
20+
21+
let bottomTitle: String? = Localization.instruction
22+
23+
var bottomSubtitle: String?
24+
25+
var accessibilityLabel: String? {
26+
return topTitle + Localization.instruction
27+
}
28+
29+
init() {}
30+
31+
func didTapPrimaryButton(in viewController: UIViewController?) {}
32+
33+
func didTapSecondaryButton(in viewController: UIViewController?) {}
34+
35+
func didTapAuxiliaryButton(in viewController: UIViewController?) {}
36+
}
37+
38+
private extension CardPresentModalBuiltInConnectingToReader {
39+
enum Localization {
40+
static let title = NSLocalizedString(
41+
"Preparing iPhone card reader...",
42+
comment: "Title label for modal dialog that appears when connecting to a built in card reader"
43+
)
44+
45+
static let instruction = NSLocalizedString(
46+
"The first time you connect, you may be prompted to accept Apple's Terms of Service.",
47+
comment: "Label within the modal dialog that appears when connecting to a built in card reader"
48+
)
49+
}
50+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import UIKit
2+
3+
/// Modal presented when we are scanning for a reader to connect to
4+
///
5+
final class CardPresentModalBuiltInReaderCheckingDeviceSupport: CardPresentPaymentsModalViewModel {
6+
/// Called when cancel button is tapped
7+
private let cancelAction: () -> Void
8+
9+
let textMode: PaymentsModalTextMode = .reducedBottomInfo
10+
let actionsMode: PaymentsModalActionsMode = .secondaryActionAndAuxiliaryButton
11+
12+
let topTitle: String = Localization.title
13+
14+
var topSubtitle: String?
15+
16+
let image: UIImage = .tempBuiltInReaderPrepare
17+
18+
let primaryButtonTitle: String? = nil
19+
20+
let secondaryButtonTitle: String? = Localization.cancel
21+
22+
let auxiliaryButtonTitle: String? = nil
23+
24+
let auxiliaryButtonimage: UIImage? = .infoOutlineImage
25+
26+
var auxiliaryAttributedButtonTitle: NSAttributedString? {
27+
let result = NSMutableAttributedString(
28+
string: .localizedStringWithFormat(
29+
Localization.learnMoreText,
30+
Localization.learnMoreLink
31+
),
32+
attributes: [.foregroundColor: UIColor.text]
33+
)
34+
result.replaceFirstOccurrence(
35+
of: Localization.learnMoreLink,
36+
with: NSAttributedString(
37+
string: Localization.learnMoreLink,
38+
attributes: [
39+
.foregroundColor: UIColor.accent,
40+
.underlineStyle: NSUnderlineStyle.single.rawValue
41+
]
42+
))
43+
result.addAttribute(.font, value: UIFont.footnote, range: NSRange(location: 0, length: result.length))
44+
return result
45+
}
46+
47+
let bottomTitle: String? = Localization.instruction
48+
49+
var bottomSubtitle: String?
50+
51+
var accessibilityLabel: String? {
52+
guard let bottomTitle = bottomTitle else {
53+
return topTitle
54+
}
55+
return topTitle + bottomTitle
56+
}
57+
58+
init(cancel: @escaping () -> Void) {
59+
self.cancelAction = cancel
60+
}
61+
62+
func didTapPrimaryButton(in viewController: UIViewController?) {}
63+
64+
func didTapSecondaryButton(in viewController: UIViewController?) {
65+
cancelAction()
66+
}
67+
68+
func didTapAuxiliaryButton(in viewController: UIViewController?) {
69+
ServiceLocator.analytics.track(.cardPresentOnboardingLearnMoreTapped)
70+
guard let viewController = viewController else {
71+
return
72+
}
73+
WebviewHelper.launch(Constants.learnMoreURL.asURL(), with: viewController)
74+
}
75+
}
76+
77+
private extension CardPresentModalBuiltInReaderCheckingDeviceSupport {
78+
enum Constants {
79+
static let learnMoreURL = WooConstants.URLs.inPersonPaymentsLearnMoreWCPay
80+
}
81+
82+
enum Localization {
83+
static let title = NSLocalizedString(
84+
"cardPresent.builtIn.modalCheckingDeviceSupport.title",
85+
value: "Checking device",
86+
comment: "Title label for modal dialog that appears when searching for a card reader"
87+
)
88+
89+
static let instruction = NSLocalizedString(
90+
"cardPresent.builtIn.modalCheckingDeviceSupport.instruction",
91+
value: "Please wait while we check that your device is ready for Tap to Pay on iPhone.",
92+
comment: "Label within the modal dialog that appears when checking the built in card reader"
93+
)
94+
95+
static let cancel = NSLocalizedString(
96+
"cardPresent.builtIn.modalCheckingDeviceSupport.cancelButton",
97+
value: "Cancel",
98+
comment: "Label for a cancel button"
99+
)
100+
101+
static let learnMoreLink = NSLocalizedString(
102+
"cardPresent.builtIn.modalCheckingDeviceSupport.learnMore.link",
103+
value: "Learn more",
104+
comment: """
105+
A label prompting users to learn more about In-Person Payments.
106+
This is the link to the website, and forms part of a longer sentence which it should be considered a part of.
107+
"""
108+
)
109+
110+
static let learnMoreText = NSLocalizedString(
111+
"cardPresent.builtIn.modalCheckingDeviceSupport.learnMore.text",
112+
value: "%1$@ about In‑Person Payments",
113+
comment: """
114+
A label prompting users to learn more about In-Person Payments.
115+
The hyphen in "In‑Person" is a non-breaking hyphen (U+2011).
116+
If your translation of that term also happens to contains a hyphen, please be sure to use the non-breaking hyphen character for it.
117+
%1$@ is a placeholder that always replaced with \"Learn more\" string,
118+
which should be translated separately and considered part of this sentence.
119+
"""
120+
)
121+
}
122+
}

WooCommerce/Classes/ViewRelated/CardPresentPayments/BuiltInCardReaderConnectionController.swift

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ final class BuiltInCardReaderConnectionController {
7474
private let alertsPresenter: CardPresentPaymentAlertsPresenting
7575
private let configuration: CardPresentPaymentsConfiguration
7676

77+
private let alertsProvider: CardReaderConnectionAlertsProviding
78+
7779
/// The reader we want the user to consider connecting to
7880
///
7981
private var candidateReader: CardReader?
@@ -105,6 +107,7 @@ final class BuiltInCardReaderConnectionController {
105107
storageManager: StorageManagerType = ServiceLocator.storageManager,
106108
stores: StoresManager = ServiceLocator.stores,
107109
alertsPresenter: CardPresentPaymentAlertsPresenting,
110+
alertsProvider: CardReaderConnectionAlertsProviding,
108111
configuration: CardPresentPaymentsConfiguration,
109112
analyticsTracker: CardReaderConnectionAnalyticsTracker
110113
) {
@@ -113,6 +116,7 @@ final class BuiltInCardReaderConnectionController {
113116
self.stores = stores
114117
state = .idle
115118
self.alertsPresenter = alertsPresenter
119+
self.alertsProvider = alertsProvider
116120
self.configuration = configuration
117121
self.analyticsTracker = analyticsTracker
118122

@@ -264,7 +268,7 @@ private extension BuiltInCardReaderConnectionController {
264268
/// If all else fails, display the "scanning" modal and
265269
/// stay in this state
266270
///
267-
alertsPresenter.present(viewModel: CardPresentModalScanningForReader(cancel: {
271+
alertsPresenter.present(viewModel: alertsProvider.scanningForReader(cancel: {
268272
self.state = .cancel
269273
}))
270274
}
@@ -288,9 +292,9 @@ private extension BuiltInCardReaderConnectionController {
288292
}
289293

290294
alertsPresenter.present(
291-
viewModel: CardPresentModalUpdateProgress(requiredUpdate: true,
292-
progress: progress,
293-
cancel: cancel))
295+
viewModel: alertsProvider.updateProgress(requiredUpdate: true,
296+
progress: progress,
297+
cancel: cancel))
294298
}
295299

296300
/// Retry a search for a card reader
@@ -382,7 +386,7 @@ private extension BuiltInCardReaderConnectionController {
382386
}
383387
stores.dispatch(action)
384388

385-
alertsPresenter.present(viewModel: CardPresentModalConnectingToReader())
389+
alertsPresenter.present(viewModel: alertsProvider.connectingToReader())
386390
}
387391

388392
/// An error occurred while connecting
@@ -402,23 +406,25 @@ private extension BuiltInCardReaderConnectionController {
402406
}
403407

404408
private func onUpdateFailed(error: Error) {
405-
guard case CardReaderServiceError.softwareUpdate(underlyingError: let underlyingError, batteryLevel: let batteryLevel) = error else {
409+
guard case CardReaderServiceError.softwareUpdate(underlyingError: let underlyingError, batteryLevel: _) = error else {
406410
return
407411
}
408412

413+
// Duplication of `readerSoftwareUpdateFailedBatteryLow` and `default is left to make factoring out easier later on.
409414
switch underlyingError {
410415
case .readerSoftwareUpdateFailedInterrupted:
411416
// Update was cancelled, don't treat this as an error
412417
return
413418
case .readerSoftwareUpdateFailedBatteryLow:
414419
alertsPresenter.present(
415-
viewModel: CardPresentModalUpdateFailedLowBattery(batteryLevel: batteryLevel,
416-
close: {
417-
self.state = .searching
418-
}))
420+
viewModel: alertsProvider.updatingFailed(tryAgain: nil,
421+
close: {
422+
self.state = .searching
423+
}))
419424
default:
420425
alertsPresenter.present(
421-
viewModel: CardPresentModalUpdateFailedNonRetryable(close: {
426+
viewModel: alertsProvider.updatingFailed(tryAgain: nil,
427+
close: {
422428
self.state = .searching
423429
}))
424430
}
@@ -429,6 +435,7 @@ private extension BuiltInCardReaderConnectionController {
429435
self.state = .retry
430436
}
431437

438+
// TODO: Consider removing this in favour of retry only – continue doesn't make sense for a built-in reader
432439
let continueSearch = {
433440
self.state = .searching
434441
}
@@ -439,30 +446,25 @@ private extension BuiltInCardReaderConnectionController {
439446

440447
guard case CardReaderServiceError.connection(let underlyingError) = error else {
441448
return alertsPresenter.present(
442-
viewModel: CardPresentModalConnectingFailed(continueSearch: continueSearch, cancelSearch: cancelSearch))
449+
viewModel: alertsProvider.connectingFailed(continueSearch: continueSearch, cancelSearch: cancelSearch))
443450
}
444451

445452
switch underlyingError {
446453
case .incompleteStoreAddress(let adminUrl):
447454
alertsPresenter.present(
448-
viewModel: CardPresentModalConnectingFailedUpdateAddress(
455+
viewModel: alertsProvider.connectingFailedIncompleteAddress(
449456
openWCSettings: openWCSettingsAction(adminUrl: adminUrl,
450457
retrySearch: retrySearch),
451458
retrySearch: retrySearch,
452459
cancelSearch: cancelSearch))
453460
case .invalidPostalCode:
454461
alertsPresenter.present(
455-
viewModel: CardPresentModalConnectingFailedUpdatePostalCode(
456-
retrySearch: retrySearch,
457-
cancelSearch: cancelSearch))
458-
case .bluetoothConnectionFailedBatteryCriticallyLow:
459-
alertsPresenter.present(
460-
viewModel: CardPresentModalConnectingFailedChargeReader(
462+
viewModel: alertsProvider.connectingFailedInvalidPostalCode(
461463
retrySearch: retrySearch,
462464
cancelSearch: cancelSearch))
463465
default:
464466
alertsPresenter.present(
465-
viewModel: CardPresentModalConnectingFailed(
467+
viewModel: alertsProvider.connectingFailed(
466468
continueSearch: continueSearch,
467469
cancelSearch: cancelSearch))
468470
}
@@ -521,7 +523,7 @@ private extension BuiltInCardReaderConnectionController {
521523
///
522524
private func onDiscoveryFailed(error: Error) {
523525
alertsPresenter.present(
524-
viewModel: CardPresentModalScanningFailed(error: error) { [weak self] in
526+
viewModel: alertsProvider.scanningFailed(error: error) { [weak self] in
525527
self?.returnFailure(error: error)
526528
})
527529
}

WooCommerce/Classes/ViewRelated/CardPresentPayments/CardPresentPaymentPreflightController.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,14 @@ final class CardPresentPaymentPreflightController {
7070
forSiteID: siteID,
7171
knownReaderProvider: CardReaderSettingsKnownReaderStorage(),
7272
alertsPresenter: alertsPresenter,
73+
alertsProvider: BluetoothReaderConnectionAlertsProvider(),
7374
configuration: configuration,
7475
analyticsTracker: analyticsTracker)
7576

7677
self.builtInConnectionController = BuiltInCardReaderConnectionController(
7778
forSiteID: siteID,
7879
alertsPresenter: alertsPresenter,
80+
alertsProvider: BuiltInReaderConnectionAlertsProvider(),
7981
configuration: configuration,
8082
analyticsTracker: analyticsTracker)
8183
}

0 commit comments

Comments
 (0)