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
12 changes: 12 additions & 0 deletions WooCommerce/Classes/Extensions/UIImage+Woo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,18 @@ extension UIImage {
return UIImage(named: "card-reader-scanning")!
}

static var tempBuiltInReaderCheck: UIImage {
return UIImage(named: "temp-woo-tap-on-mobile-check")!
}

static var tempBuiltInReaderPrepare: UIImage {
return UIImage(named: "temp-woo-tap-on-mobile-prepare")!
}

static var tempBuiltInReaderPayment: UIImage {
return UIImage(named: "temp-woo-tap-on-mobile")!
}

/// Found Card Reader
///
static var cardReaderFound: UIImage {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import UIKit

/// Modal presented when we are connecting to a reader
///
final class CardPresentModalBuiltInConnectingToReader: CardPresentPaymentsModalViewModel {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A non-blocker suggestion here, I would make these namings easier to understand, perhaps CardPresentBuiltInConnectingToReaderModalViewModel and CardPresentBuiltInReaderCheckingDeviceSupportModalViewModel? I see they are long, so I leave it to your discernment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I'll put a note on the issue to update these when we have finalised strings and images for the screens: there's not really a lot of clarity over what the SDK is actually doing at each point, so naming them based on the message is probably the clearest approach.

let textMode: PaymentsModalTextMode = .reducedBottomInfo
let actionsMode: PaymentsModalActionsMode = .none

let topTitle: String = Localization.title

var topSubtitle: String?

let image: UIImage = .tempBuiltInReaderCheck

let primaryButtonTitle: String? = nil

let secondaryButtonTitle: String? = nil

let auxiliaryButtonTitle: String? = nil

let bottomTitle: String? = Localization.instruction

var bottomSubtitle: String?

var accessibilityLabel: String? {
return topTitle + Localization.instruction
}

init() {}

func didTapPrimaryButton(in viewController: UIViewController?) {}

func didTapSecondaryButton(in viewController: UIViewController?) {}

func didTapAuxiliaryButton(in viewController: UIViewController?) {}
}

private extension CardPresentModalBuiltInConnectingToReader {
enum Localization {
static let title = NSLocalizedString(
"Preparing iPhone card reader...",
comment: "Title label for modal dialog that appears when connecting to a built in card reader"
)

static let instruction = NSLocalizedString(
"The first time you connect, you may be prompted to accept Apple's Terms of Service.",
comment: "Label within the modal dialog that appears when connecting to a built in card reader"
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import UIKit

/// Modal presented when we are scanning for a reader to connect to
///
final class CardPresentModalBuiltInReaderCheckingDeviceSupport: CardPresentPaymentsModalViewModel {
/// Called when cancel button is tapped
private let cancelAction: () -> Void

let textMode: PaymentsModalTextMode = .reducedBottomInfo
let actionsMode: PaymentsModalActionsMode = .secondaryActionAndAuxiliaryButton

let topTitle: String = Localization.title

var topSubtitle: String?

let image: UIImage = .tempBuiltInReaderPrepare

let primaryButtonTitle: String? = nil

let secondaryButtonTitle: String? = Localization.cancel

let auxiliaryButtonTitle: String? = nil

let auxiliaryButtonimage: UIImage? = .infoOutlineImage

var auxiliaryAttributedButtonTitle: NSAttributedString? {
let result = NSMutableAttributedString(
string: .localizedStringWithFormat(
Localization.learnMoreText,
Localization.learnMoreLink
),
attributes: [.foregroundColor: UIColor.text]
)
result.replaceFirstOccurrence(
of: Localization.learnMoreLink,
with: NSAttributedString(
string: Localization.learnMoreLink,
attributes: [
.foregroundColor: UIColor.accent,
.underlineStyle: NSUnderlineStyle.single.rawValue
]
))
result.addAttribute(.font, value: UIFont.footnote, range: NSRange(location: 0, length: result.length))
return result
}

let bottomTitle: String? = Localization.instruction

var bottomSubtitle: String?

var accessibilityLabel: String? {
guard let bottomTitle = bottomTitle else {
return topTitle
}
return topTitle + bottomTitle
}

init(cancel: @escaping () -> Void) {
self.cancelAction = cancel
}

func didTapPrimaryButton(in viewController: UIViewController?) {}

func didTapSecondaryButton(in viewController: UIViewController?) {
cancelAction()
}

func didTapAuxiliaryButton(in viewController: UIViewController?) {
ServiceLocator.analytics.track(.cardPresentOnboardingLearnMoreTapped)
guard let viewController = viewController else {
return
}
WebviewHelper.launch(Constants.learnMoreURL.asURL(), with: viewController)
}
}

private extension CardPresentModalBuiltInReaderCheckingDeviceSupport {
enum Constants {
static let learnMoreURL = WooConstants.URLs.inPersonPaymentsLearnMoreWCPay
}

enum Localization {
static let title = NSLocalizedString(
"cardPresent.builtIn.modalCheckingDeviceSupport.title",
value: "Checking device",
comment: "Title label for modal dialog that appears when searching for a card reader"
)

static let instruction = NSLocalizedString(
"cardPresent.builtIn.modalCheckingDeviceSupport.instruction",
value: "Please wait while we check that your device is ready for Tap to Pay on iPhone.",
comment: "Label within the modal dialog that appears when checking the built in card reader"
)

static let cancel = NSLocalizedString(
"cardPresent.builtIn.modalCheckingDeviceSupport.cancelButton",
value: "Cancel",
comment: "Label for a cancel button"
)

static let learnMoreLink = NSLocalizedString(
"cardPresent.builtIn.modalCheckingDeviceSupport.learnMore.link",
value: "Learn more",
comment: """
A label prompting users to learn more about In-Person Payments.
This is the link to the website, and forms part of a longer sentence which it should be considered a part of.
"""
)

static let learnMoreText = NSLocalizedString(
"cardPresent.builtIn.modalCheckingDeviceSupport.learnMore.text",
value: "%1$@ about In‑Person Payments",
comment: """
A label prompting users to learn more about In-Person Payments.
The hyphen in "In‑Person" is a non-breaking hyphen (U+2011).
If your translation of that term also happens to contains a hyphen, please be sure to use the non-breaking hyphen character for it.
%1$@ is a placeholder that always replaced with \"Learn more\" string,
which should be translated separately and considered part of this sentence.
"""
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ final class BuiltInCardReaderConnectionController {
private let alertsPresenter: CardPresentPaymentAlertsPresenting
private let configuration: CardPresentPaymentsConfiguration

private let alertsProvider: CardReaderConnectionAlertsProviding

/// The reader we want the user to consider connecting to
///
private var candidateReader: CardReader?
Expand Down Expand Up @@ -105,6 +107,7 @@ final class BuiltInCardReaderConnectionController {
storageManager: StorageManagerType = ServiceLocator.storageManager,
stores: StoresManager = ServiceLocator.stores,
alertsPresenter: CardPresentPaymentAlertsPresenting,
alertsProvider: CardReaderConnectionAlertsProviding,
configuration: CardPresentPaymentsConfiguration,
analyticsTracker: CardReaderConnectionAnalyticsTracker
) {
Expand All @@ -113,6 +116,7 @@ final class BuiltInCardReaderConnectionController {
self.stores = stores
state = .idle
self.alertsPresenter = alertsPresenter
self.alertsProvider = alertsProvider
self.configuration = configuration
self.analyticsTracker = analyticsTracker

Expand Down Expand Up @@ -264,7 +268,7 @@ private extension BuiltInCardReaderConnectionController {
/// If all else fails, display the "scanning" modal and
/// stay in this state
///
alertsPresenter.present(viewModel: CardPresentModalScanningForReader(cancel: {
alertsPresenter.present(viewModel: alertsProvider.scanningForReader(cancel: {
self.state = .cancel
}))
}
Expand All @@ -288,9 +292,9 @@ private extension BuiltInCardReaderConnectionController {
}

alertsPresenter.present(
viewModel: CardPresentModalUpdateProgress(requiredUpdate: true,
progress: progress,
cancel: cancel))
viewModel: alertsProvider.updateProgress(requiredUpdate: true,
progress: progress,
cancel: cancel))
}

/// Retry a search for a card reader
Expand Down Expand Up @@ -382,7 +386,7 @@ private extension BuiltInCardReaderConnectionController {
}
stores.dispatch(action)

alertsPresenter.present(viewModel: CardPresentModalConnectingToReader())
alertsPresenter.present(viewModel: alertsProvider.connectingToReader())
}

/// An error occurred while connecting
Expand All @@ -402,23 +406,25 @@ private extension BuiltInCardReaderConnectionController {
}

private func onUpdateFailed(error: Error) {
guard case CardReaderServiceError.softwareUpdate(underlyingError: let underlyingError, batteryLevel: let batteryLevel) = error else {
guard case CardReaderServiceError.softwareUpdate(underlyingError: let underlyingError, batteryLevel: _) = error else {
return
}

// Duplication of `readerSoftwareUpdateFailedBatteryLow` and `default is left to make factoring out easier later on.
switch underlyingError {
case .readerSoftwareUpdateFailedInterrupted:
// Update was cancelled, don't treat this as an error
return
case .readerSoftwareUpdateFailedBatteryLow:
alertsPresenter.present(
viewModel: CardPresentModalUpdateFailedLowBattery(batteryLevel: batteryLevel,
close: {
self.state = .searching
}))
viewModel: alertsProvider.updatingFailed(tryAgain: nil,
close: {
self.state = .searching
}))
default:
alertsPresenter.present(
viewModel: CardPresentModalUpdateFailedNonRetryable(close: {
viewModel: alertsProvider.updatingFailed(tryAgain: nil,
close: {
self.state = .searching
}))
}
Expand All @@ -429,6 +435,7 @@ private extension BuiltInCardReaderConnectionController {
self.state = .retry
}

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

guard case CardReaderServiceError.connection(let underlyingError) = error else {
return alertsPresenter.present(
viewModel: CardPresentModalConnectingFailed(continueSearch: continueSearch, cancelSearch: cancelSearch))
viewModel: alertsProvider.connectingFailed(continueSearch: continueSearch, cancelSearch: cancelSearch))
}

switch underlyingError {
case .incompleteStoreAddress(let adminUrl):
alertsPresenter.present(
viewModel: CardPresentModalConnectingFailedUpdateAddress(
viewModel: alertsProvider.connectingFailedIncompleteAddress(
openWCSettings: openWCSettingsAction(adminUrl: adminUrl,
retrySearch: retrySearch),
retrySearch: retrySearch,
cancelSearch: cancelSearch))
case .invalidPostalCode:
alertsPresenter.present(
viewModel: CardPresentModalConnectingFailedUpdatePostalCode(
retrySearch: retrySearch,
cancelSearch: cancelSearch))
case .bluetoothConnectionFailedBatteryCriticallyLow:
alertsPresenter.present(
viewModel: CardPresentModalConnectingFailedChargeReader(
viewModel: alertsProvider.connectingFailedInvalidPostalCode(
retrySearch: retrySearch,
cancelSearch: cancelSearch))
default:
alertsPresenter.present(
viewModel: CardPresentModalConnectingFailed(
viewModel: alertsProvider.connectingFailed(
continueSearch: continueSearch,
cancelSearch: cancelSearch))
}
Expand Down Expand Up @@ -521,7 +523,7 @@ private extension BuiltInCardReaderConnectionController {
///
private func onDiscoveryFailed(error: Error) {
alertsPresenter.present(
viewModel: CardPresentModalScanningFailed(error: error) { [weak self] in
viewModel: alertsProvider.scanningFailed(error: error) { [weak self] in
self?.returnFailure(error: error)
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@ final class CardPresentPaymentPreflightController {
forSiteID: siteID,
knownReaderProvider: CardReaderSettingsKnownReaderStorage(),
alertsPresenter: alertsPresenter,
alertsProvider: BluetoothReaderConnectionAlertsProvider(),
configuration: configuration,
analyticsTracker: analyticsTracker)

self.builtInConnectionController = BuiltInCardReaderConnectionController(
forSiteID: siteID,
alertsPresenter: alertsPresenter,
alertsProvider: BuiltInReaderConnectionAlertsProvider(),
configuration: configuration,
analyticsTracker: analyticsTracker)
}
Expand Down
Loading