From fd5a6e0c73426b1fbb87ed167c04b0aee710fc18 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Tue, 1 Jul 2025 16:38:13 -0400 Subject: [PATCH 01/12] Show suggestion to fix each ineligible reason. --- .../POS/TabBar/POSIneligibleView.swift | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index 0131f547719..73d54d297b7 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -34,6 +34,11 @@ struct POSIneligibleView: View { .multilineTextAlignment(.center) .foregroundColor(Color.posOnSurface) + Text(suggestionText) + .font(POSFontStyle.posCaptionRegular.font()) + .multilineTextAlignment(.center) + .foregroundColor(Color.posOnSurface) + Button { Task { @MainActor in do { @@ -41,7 +46,7 @@ struct POSIneligibleView: View { try await onRefresh() isLoading = false } catch { - // TODO-jc: handle error if needed, e.g., show an error message + // TODO: WOOMOB-720 - handle error if needed, e.g., show an error message print("Error refreshing eligibility: \(error)") isLoading = false } @@ -103,6 +108,57 @@ struct POSIneligibleView: View { return Localization.defaultReason } } + + private var suggestionText: String { + switch reason { + case .notTablet: + return NSLocalizedString("pos.ineligible.suggestion.notTablet", + value: "Please use an iPad to access POS features.", + comment: "Suggestion for not tablet: use iPad") + case .unsupportedIOSVersion: + return NSLocalizedString("pos.ineligible.suggestion.unsupportedIOSVersion", + value: "Update your device to iOS 17 or later in Settings > General > Software Update.", + comment: "Suggestion for unsupported iOS version: update iOS") + case .unsupportedWooCommerceVersion: + return NSLocalizedString("pos.ineligible.suggestion.unsupportedWooCommerceVersion", + value: "Go to your WordPress admin and update WooCommerce to the latest version.", + comment: "Suggestion for unsupported WooCommerce version: update plugin") + case .wooCommercePluginNotFound: + return NSLocalizedString("pos.ineligible.suggestion.wooCommercePluginNotFound", + value: "Install and activate the WooCommerce plugin from your WordPress admin.", + comment: "Suggestion for missing WooCommerce plugin: install plugin") + case .featureSwitchDisabled: + return NSLocalizedString("pos.ineligible.suggestion.featureSwitchDisabled", + value: "Enable the POS feature from your WordPress admin under WooCommerce settings > Advanced > Features.", + comment: "Suggestion for disabled feature switch: enable feature in WooCommerce settings") + case .featureSwitchSyncFailure: + return NSLocalizedString("pos.ineligible.suggestion.featureSwitchSyncFailure", + value: "Try relaunching the app or check your internet connection and try again.", + comment: "Suggestion for feature switch sync failure: relaunch or check connection") + case .unsupportedCountry: + // TODO: DI countries + return NSLocalizedString("pos.ineligible.suggestion.unsupportedCountry", + value: "POS is currently only available in select countries. Check back later for availability in your region.", + comment: "Suggestion for unsupported country: check back later") + case .unsupportedCurrency: + // TODO: DI currencies + return NSLocalizedString("pos.ineligible.suggestion.unsupportedCurrency", + value: "Change your store's currency to USD, EUR, GBP, or CAD in WooCommerce settings.", + comment: "Suggestion for unsupported currency: change currency") + case .siteSettingsNotAvailable: + return NSLocalizedString("pos.ineligible.suggestion.siteSettingsNotAvailable", + value: "Check your internet connection and try relaunching the app. If the issue persists, please contact support.", + comment: "Suggestion for site settings unavailable: check connection or contact support") + case .featureFlagDisabled: + return NSLocalizedString("pos.ineligible.suggestion.featureFlagDisabled", + value: "POS is currently disabled.", + comment: "Suggestion for disabled feature flag: notify that POS is disabled remotely") + case .selfDeallocated: + return NSLocalizedString("pos.ineligible.suggestion.selfDeallocated", + value: "Try relaunching the app to resolve this issue.", + comment: "Suggestion for self deallocated: relaunch") + } + } } private extension POSIneligibleView { From c186f7fc33ce494e35ac2957b467b54116114d53 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 2 Jul 2025 13:25:00 -0400 Subject: [PATCH 02/12] DI supported countries/currencies to POSIneligibleReason `unsupportedCountry`/`unsupportedCurrency` cases. --- .../POS/TabBar/POSIneligibleView.swift | 101 +++++++++++++++--- .../POS/POSTabEligibilityChecker.swift | 27 +++-- .../POS/POSTabEligibilityCheckerTests.swift | 27 ++--- 3 files changed, 117 insertions(+), 38 deletions(-) diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index 73d54d297b7..1d7266977f7 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -135,16 +135,26 @@ struct POSIneligibleView: View { return NSLocalizedString("pos.ineligible.suggestion.featureSwitchSyncFailure", value: "Try relaunching the app or check your internet connection and try again.", comment: "Suggestion for feature switch sync failure: relaunch or check connection") - case .unsupportedCountry: - // TODO: DI countries - return NSLocalizedString("pos.ineligible.suggestion.unsupportedCountry", - value: "POS is currently only available in select countries. Check back later for availability in your region.", - comment: "Suggestion for unsupported country: check back later") - case .unsupportedCurrency: - // TODO: DI currencies - return NSLocalizedString("pos.ineligible.suggestion.unsupportedCurrency", - value: "Change your store's currency to USD, EUR, GBP, or CAD in WooCommerce settings.", - comment: "Suggestion for unsupported currency: change currency") + case let .unsupportedCountry(supportedCountries): + let countryNames = supportedCountries.map { $0.readableCountry } + let formattedCountryList = ListFormatter.localizedString(byJoining: countryNames) + let format = NSLocalizedString( + "pos.ineligible.suggestion.unsupportedCountry", + value: "POS is currently only available in %1$@. Check back later for availability in your region.", + comment: "Suggestion for unsupported country with list of supported countries. " + + "%1$@ is a placeholder for the localized list of supported country names." + ) + return String.localizedStringWithFormat(format, formattedCountryList) + case let .unsupportedCurrency(supportedCurrencies): + let currencyList = supportedCurrencies.map { $0.rawValue } + let formattedCurrencyList = ListFormatter.localizedString(byJoining: currencyList) + let format = NSLocalizedString( + "pos.ineligible.suggestion.unsupportedCurrency", + value: "The POS system is not available for your store’s currency. It currently supports only %1$@. Please check your store currency settings or contact support for assistance.", + comment: "Suggestion for unsupported currency with list of supported currencies. " + + "%1$@ is a placeholder for the localized list of supported currency codes." + ) + return String.localizedStringWithFormat(format, formattedCurrencyList) case .siteSettingsNotAvailable: return NSLocalizedString("pos.ineligible.suggestion.siteSettingsNotAvailable", value: "Check your internet connection and try relaunching the app. If the issue persists, please contact support.", @@ -178,9 +188,76 @@ private extension POSIneligibleView { } } -#Preview { +#if DEBUG + +#Preview("Unsupported currency") { + POSIneligibleView( + reason: .unsupportedCurrency(supportedCurrencies: [.USD]), + onRefresh: {} + ) +} + +#Preview("Unsupported country") { + POSIneligibleView( + reason: .unsupportedCountry(supportedCountries: [.US, .GB]), + onRefresh: {} + ) +} + +#Preview("Not a tablet") { POSIneligibleView( - reason: .unsupportedCurrency, + reason: .notTablet, onRefresh: {} ) } + +#Preview("Unsupported iOS version") { + POSIneligibleView( + reason: .unsupportedIOSVersion, + onRefresh: {} + ) +} + +#Preview("WooCommerce plugin not found") { + POSIneligibleView( + reason: .wooCommercePluginNotFound, + onRefresh: {} + ) +} + +#Preview("Feature flag disabled") { + POSIneligibleView( + reason: .featureFlagDisabled, + onRefresh: {} + ) +} + +#Preview("Feature switch disabled") { + POSIneligibleView( + reason: .featureSwitchDisabled, + onRefresh: {} + ) +} + +#Preview("Site settings unavailable") { + POSIneligibleView( + reason: .siteSettingsNotAvailable, + onRefresh: {} + ) +} + +#Preview("Feature switch sync failure") { + POSIneligibleView( + reason: .featureSwitchSyncFailure, + onRefresh: {} + ) +} + +#Preview("Unsupported WooCommerce version") { + POSIneligibleView( + reason: .unsupportedWooCommerceVersion, + onRefresh: {} + ) +} + +#endif diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift index 8278571aeaa..b89b0501fec 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift @@ -25,8 +25,8 @@ enum POSIneligibleReason: Equatable { case featureFlagDisabled case featureSwitchDisabled case featureSwitchSyncFailure - case unsupportedCountry - case unsupportedCurrency + case unsupportedCountry(supportedCountries: [CountryCode]) + case unsupportedCurrency(supportedCurrencies: [CurrencyCode]) case selfDeallocated } @@ -153,7 +153,7 @@ private extension POSTabEligibilityChecker { case .eligible: break case let .ineligible(reason): - if reason == .unsupportedCurrency { + if case .unsupportedCurrency = reason { break } else { return false @@ -250,21 +250,20 @@ private extension POSTabEligibilityChecker { } func isEligibleFromCountryAndCurrencyCode(countryCode: CountryCode, currencyCode: CurrencyCode) -> POSEligibilityState { + let supportedCountries: [CountryCode] = [.US, .GB] + let supportedCurrencies: [CountryCode: [CurrencyCode]] = [.US: [.USD], + .GB: [.GBP]] + // Checks country first. - switch countryCode { - case .US, .GB: - break - default: - return .ineligible(reason: .unsupportedCountry) + guard supportedCountries.contains(countryCode) else { + return .ineligible(reason: .unsupportedCountry(supportedCountries: supportedCountries)) } - // Then checks currency based on the country. - switch (countryCode, currencyCode) { - case (.US, .USD), (.GB, .GBP): - return .eligible - default: - return .ineligible(reason: .unsupportedCurrency) + let supportedCurrenciesForCountry = supportedCurrencies[countryCode] ?? [] + guard supportedCurrenciesForCountry.contains(currencyCode) else { + return .ineligible(reason: .unsupportedCurrency(supportedCurrencies: supportedCurrenciesForCountry)) } + return .eligible } } diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift index e0318b4e824..9f30e96ac4e 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift @@ -131,20 +131,23 @@ struct POSTabEligibilityCheckerTests { let result = await checker.checkEligibility() // Then - #expect(result == .ineligible(reason: .unsupportedCountry)) + #expect(result == .ineligible(reason: .unsupportedCountry(supportedCountries: [.US, .GB]))) } @Test(arguments: [ - (country: Country.us, currency: CurrencyCode.GBP, isPointOfSaleAsATabi2Enabled: true), - (country: Country.us, currency: CurrencyCode.GBP, isPointOfSaleAsATabi2Enabled: false), - (country: Country.us, currency: CurrencyCode.CAD, isPointOfSaleAsATabi2Enabled: true), - (country: Country.us, currency: CurrencyCode.CAD, isPointOfSaleAsATabi2Enabled: false), - (country: Country.gb, currency: CurrencyCode.EUR, isPointOfSaleAsATabi2Enabled: true), - (country: Country.gb, currency: CurrencyCode.EUR, isPointOfSaleAsATabi2Enabled: false), - (country: Country.gb, currency: CurrencyCode.USD, isPointOfSaleAsATabi2Enabled: true), - (country: Country.gb, currency: CurrencyCode.USD, isPointOfSaleAsATabi2Enabled: false) + (country: Country.us, currency: CurrencyCode.GBP, expectedSupportedCurrencies: [CurrencyCode.USD], isPointOfSaleAsATabi2Enabled: true), + (country: Country.us, currency: CurrencyCode.GBP, expectedSupportedCurrencies: [CurrencyCode.USD], isPointOfSaleAsATabi2Enabled: false), + (country: Country.us, currency: CurrencyCode.CAD, expectedSupportedCurrencies: [CurrencyCode.USD], isPointOfSaleAsATabi2Enabled: true), + (country: Country.us, currency: CurrencyCode.CAD, expectedSupportedCurrencies: [CurrencyCode.USD], isPointOfSaleAsATabi2Enabled: false), + (country: Country.gb, currency: CurrencyCode.EUR, expectedSupportedCurrencies: [CurrencyCode.GBP], isPointOfSaleAsATabi2Enabled: true), + (country: Country.gb, currency: CurrencyCode.EUR, expectedSupportedCurrencies: [CurrencyCode.GBP], isPointOfSaleAsATabi2Enabled: false), + (country: Country.gb, currency: CurrencyCode.USD, expectedSupportedCurrencies: [CurrencyCode.GBP], isPointOfSaleAsATabi2Enabled: true), + (country: Country.gb, currency: CurrencyCode.USD, expectedSupportedCurrencies: [CurrencyCode.GBP], isPointOfSaleAsATabi2Enabled: false) ]) - fileprivate func is_ineligible_when_currency_is_not_supported(country: Country, currency: CurrencyCode, isPointOfSaleAsATabi2Enabled: Bool) async throws { + fileprivate func is_ineligible_when_currency_is_not_supported(country: Country, + currency: CurrencyCode, + expectedSupportedCurrencies: [CurrencyCode], + isPointOfSaleAsATabi2Enabled: Bool) async throws { // Given let featureFlagService = MockFeatureFlagService(isPointOfSaleAsATabi2Enabled: isPointOfSaleAsATabi2Enabled) setupCountry(country: country, currency: currency) @@ -160,7 +163,7 @@ struct POSTabEligibilityCheckerTests { let result = await checker.checkEligibility() // Then - #expect(result == .ineligible(reason: .unsupportedCurrency)) + #expect(result == .ineligible(reason: .unsupportedCurrency(supportedCurrencies: expectedSupportedCurrencies))) } @Test(arguments: [true, false]) @@ -342,7 +345,7 @@ struct POSTabEligibilityCheckerTests { let result = await checker.checkEligibility() // Then - Should be ineligible because fresh settings show CA (not cached US) - #expect(result == .ineligible(reason: .unsupportedCountry)) + #expect(result == .ineligible(reason: .unsupportedCountry(supportedCountries: [.US, .GB]))) } @Test(arguments: [true, false]) From 1f63381313d8843a5044c613f2849345a9957352 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 2 Jul 2025 13:39:17 -0400 Subject: [PATCH 03/12] Update copy that is available from design. --- .../Classes/POS/TabBar/POSIneligibleView.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index 1d7266977f7..e5aad9d70b0 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -113,15 +113,17 @@ struct POSIneligibleView: View { switch reason { case .notTablet: return NSLocalizedString("pos.ineligible.suggestion.notTablet", - value: "Please use an iPad to access POS features.", + value: "Please use a tablet to access POS features.", comment: "Suggestion for not tablet: use iPad") case .unsupportedIOSVersion: return NSLocalizedString("pos.ineligible.suggestion.unsupportedIOSVersion", - value: "Update your device to iOS 17 or later in Settings > General > Software Update.", + value: "The POS system requires iOS 17 or later. Please update your device to iOS 17+ to use this feature.", comment: "Suggestion for unsupported iOS version: update iOS") case .unsupportedWooCommerceVersion: + // TODO: DI min WC version return NSLocalizedString("pos.ineligible.suggestion.unsupportedWooCommerceVersion", - value: "Go to your WordPress admin and update WooCommerce to the latest version.", + value: "Your WooCommerce version is not supported. " + + "The POS system requires WooCommerce version ### or above. Please update WooCommerce to the latest version.", comment: "Suggestion for unsupported WooCommerce version: update plugin") case .wooCommercePluginNotFound: return NSLocalizedString("pos.ineligible.suggestion.wooCommercePluginNotFound", @@ -129,7 +131,7 @@ struct POSIneligibleView: View { comment: "Suggestion for missing WooCommerce plugin: install plugin") case .featureSwitchDisabled: return NSLocalizedString("pos.ineligible.suggestion.featureSwitchDisabled", - value: "Enable the POS feature from your WordPress admin under WooCommerce settings > Advanced > Features.", + value: "The POS core feature must be enabled to proceed. Please enable the POS feature from your WordPress admin under WooCommerce settings > Advanced > Features.", comment: "Suggestion for disabled feature switch: enable feature in WooCommerce settings") case .featureSwitchSyncFailure: return NSLocalizedString("pos.ineligible.suggestion.featureSwitchSyncFailure", @@ -175,7 +177,7 @@ private extension POSIneligibleView { enum Localization { static let refreshEligibility = NSLocalizedString( "pos.ineligible.refresh.button.title", - value: "Check Eligibility Again", + value: "Retry", comment: "Button title to refresh POS eligibility check" ) From a2b5591b6b11b242655662f5c13651b974722475 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 2 Jul 2025 13:47:03 -0400 Subject: [PATCH 04/12] Updates based on design. --- .../POS/TabBar/POSIneligibleView.swift | 100 ++++++------------ 1 file changed, 33 insertions(+), 67 deletions(-) diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index e5aad9d70b0..e33093b28a9 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -9,36 +9,34 @@ struct POSIneligibleView: View { @State private var isLoading: Bool = false var body: some View { - VStack(spacing: POSSpacing.large) { - HStack { - Spacer() - Button { - dismiss() - } label: { - Text(Image(systemName: "xmark")) - .font(POSFontStyle.posButtonSymbolLarge.font()) - } - .foregroundColor(Color.posOnSurfaceVariantLowest) - } - + VStack(spacing: 0) { Spacer() - VStack(spacing: POSSpacing.medium) { + VStack(alignment: .center, spacing: 0) { Image(PointOfSaleAssets.exclamationMark.imageName) .resizable() .frame(width: POSErrorAndAlertIconSize.large.dimension, height: POSErrorAndAlertIconSize.large.dimension) - Text(reasonText) + Spacer() + .frame(height: POSSpacing.medium) + + Text(Localization.title) .font(POSFontStyle.posHeadingBold.font()) .multilineTextAlignment(.center) .foregroundColor(Color.posOnSurface) + Spacer() + .frame(height: POSSpacing.small) + Text(suggestionText) - .font(POSFontStyle.posCaptionRegular.font()) + .font(POSFontStyle.posBodyLargeRegular().font()) .multilineTextAlignment(.center) .foregroundColor(Color.posOnSurface) + Spacer() + .frame(height: POSSpacing.large) + Button { Task { @MainActor in do { @@ -55,6 +53,16 @@ struct POSIneligibleView: View { Text(Localization.refreshEligibility) } .buttonStyle(POSFilledButtonStyle(size: .normal, isLoading: isLoading)) + + Spacer() + .frame(height: POSSpacing.medium) + + Button { + dismiss() + } label: { + Text(Localization.dismiss) + } + .buttonStyle(POSOutlinedButtonStyle(size: .normal)) } Spacer() @@ -62,53 +70,6 @@ struct POSIneligibleView: View { .padding(POSPadding.large) } - private var reasonText: String { - switch reason { - case .notTablet: - return NSLocalizedString("pos.ineligible.reason.notTablet", - value: "POS is only available on iPad.", - comment: "Ineligible reason: not a tablet") - case .unsupportedIOSVersion: - return NSLocalizedString("pos.ineligible.reason.unsupportedIOSVersion", - value: "POS requires a newer version of iOS 17 and above.", - comment: "Ineligible reason: iOS version too low") - case .unsupportedWooCommerceVersion: - return NSLocalizedString("pos.ineligible.reason.unsupportedWooCommerceVersion", - value: "Please update WooCommerce plugin to use POS.", - comment: "Ineligible reason: WooCommerce version too low") - case .wooCommercePluginNotFound: - return NSLocalizedString("pos.ineligible.reason.wooCommercePluginNotFound", - value: "WooCommerce plugin not found.", - comment: "Ineligible reason: plugin missing") - case .featureSwitchDisabled: - return NSLocalizedString("pos.ineligible.reason.featureSwitchDisabled", - value: "POS feature is not enabled for your store.", - comment: "Ineligible reason: feature switch off") - case .featureSwitchSyncFailure: - return NSLocalizedString("pos.ineligible.reason.featureSwitchSyncFailure", - value: "Could not verify POS feature status.", - comment: "Ineligible reason: feature switch sync failed") - case .unsupportedCountry: - return NSLocalizedString("pos.ineligible.reason.unsupportedCountry", - value: "POS is not available in your country.", - comment: "Ineligible reason: country not supported") - case .unsupportedCurrency: - return NSLocalizedString("pos.ineligible.reason.unsupportedCurrency", - value: "POS is not available for your store's currency.", - comment: "Ineligible reason: currency not supported") - case .siteSettingsNotAvailable: - return NSLocalizedString("pos.ineligible.reason.siteSettingsNotAvailable", - value: "Unable to load store settings for POS.", - comment: "Ineligible reason: site settings unavailable") - case .featureFlagDisabled: - return NSLocalizedString("pos.ineligible.reason.featureFlagDisabled", - value: "POS feature is currently disabled.", - comment: "Ineligible reason: feature flag disabled") - case .selfDeallocated: - return Localization.defaultReason - } - } - private var suggestionText: String { switch reason { case .notTablet: @@ -175,17 +136,22 @@ struct POSIneligibleView: View { private extension POSIneligibleView { enum Localization { + static let title = NSLocalizedString( + "pos.ineligible.title", + value: "Unable to load", + comment: "Title shown in POS ineligible view" + ) + static let refreshEligibility = NSLocalizedString( "pos.ineligible.refresh.button.title", value: "Retry", comment: "Button title to refresh POS eligibility check" ) - /// Default message shown when POS eligibility reason is not available. - static let defaultReason = NSLocalizedString( - "pos.ineligible.default.reason", - value: "Your store is not eligible for POS at this time.", - comment: "Default message shown when POS eligibility reason is not available" + static let dismiss = NSLocalizedString( + "pos.ineligible.dismiss.button.title", + value: "Exit POS", + comment: "Button title to dismiss POS ineligible view" ) } } From ab94a2ccf1dad3fadda2a520c50951aa3876f2c2 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 2 Jul 2025 14:13:35 -0400 Subject: [PATCH 05/12] Update buttons to be 0.5 parent width. --- .../POS/TabBar/POSIneligibleView.swift | 148 ++++++++++-------- 1 file changed, 86 insertions(+), 62 deletions(-) diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index e33093b28a9..2b5d79e40c8 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -2,6 +2,7 @@ import SwiftUI /// A view that displays when the Point of Sale (POS) feature is not available for the current store. /// Shows the specific reason why POS is ineligible and provides a button to re-check eligibility. +@available(iOS 17.0, *) struct POSIneligibleView: View { let reason: POSIneligibleReason let onRefresh: () async throws -> Void @@ -37,32 +38,34 @@ struct POSIneligibleView: View { Spacer() .frame(height: POSSpacing.large) - Button { - Task { @MainActor in - do { - isLoading = true - try await onRefresh() - isLoading = false - } catch { - // TODO: WOOMOB-720 - handle error if needed, e.g., show an error message - print("Error refreshing eligibility: \(error)") - isLoading = false + VStack(spacing: POSSpacing.medium) { + Button { + Task { @MainActor in + do { + isLoading = true + try await onRefresh() + isLoading = false + } catch { + // TODO: WOOMOB-720 - handle error if needed, e.g., show an error message + print("Error refreshing eligibility: \(error)") + isLoading = false + } } + } label: { + Text(Localization.refreshEligibility) } - } label: { - Text(Localization.refreshEligibility) - } - .buttonStyle(POSFilledButtonStyle(size: .normal, isLoading: isLoading)) - - Spacer() - .frame(height: POSSpacing.medium) + .buttonStyle(POSFilledButtonStyle(size: .normal, isLoading: isLoading)) - Button { - dismiss() - } label: { - Text(Localization.dismiss) + Button { + dismiss() + } label: { + Text(Localization.dismiss) + } + .buttonStyle(POSOutlinedButtonStyle(size: .normal)) + } + .containerRelativeFrame(.horizontal) { length, _ in + length * 0.5 } - .buttonStyle(POSOutlinedButtonStyle(size: .normal)) } Spacer() @@ -134,6 +137,7 @@ struct POSIneligibleView: View { } } +@available(iOS 17.0, *) private extension POSIneligibleView { enum Localization { static let title = NSLocalizedString( @@ -159,73 +163,93 @@ private extension POSIneligibleView { #if DEBUG #Preview("Unsupported currency") { - POSIneligibleView( - reason: .unsupportedCurrency(supportedCurrencies: [.USD]), - onRefresh: {} - ) + if #available(iOS 17.0, *) { + POSIneligibleView( + reason: .unsupportedCurrency(supportedCurrencies: [.USD]), + onRefresh: {} + ) + } } #Preview("Unsupported country") { - POSIneligibleView( - reason: .unsupportedCountry(supportedCountries: [.US, .GB]), - onRefresh: {} - ) + if #available(iOS 17.0, *) { + POSIneligibleView( + reason: .unsupportedCountry(supportedCountries: [.US, .GB]), + onRefresh: {} + ) + } } #Preview("Not a tablet") { - POSIneligibleView( - reason: .notTablet, - onRefresh: {} - ) + if #available(iOS 17.0, *) { + POSIneligibleView( + reason: .notTablet, + onRefresh: {} + ) + } } #Preview("Unsupported iOS version") { - POSIneligibleView( - reason: .unsupportedIOSVersion, - onRefresh: {} - ) + if #available(iOS 17.0, *) { + POSIneligibleView( + reason: .unsupportedIOSVersion, + onRefresh: {} + ) + } } #Preview("WooCommerce plugin not found") { - POSIneligibleView( - reason: .wooCommercePluginNotFound, - onRefresh: {} - ) + if #available(iOS 17.0, *) { + POSIneligibleView( + reason: .wooCommercePluginNotFound, + onRefresh: {} + ) + } } #Preview("Feature flag disabled") { - POSIneligibleView( - reason: .featureFlagDisabled, - onRefresh: {} - ) + if #available(iOS 17.0, *) { + POSIneligibleView( + reason: .featureFlagDisabled, + onRefresh: {} + ) + } } #Preview("Feature switch disabled") { - POSIneligibleView( - reason: .featureSwitchDisabled, - onRefresh: {} - ) + if #available(iOS 17.0, *) { + POSIneligibleView( + reason: .featureSwitchDisabled, + onRefresh: {} + ) + } } #Preview("Site settings unavailable") { - POSIneligibleView( - reason: .siteSettingsNotAvailable, - onRefresh: {} - ) + if #available(iOS 17.0, *) { + POSIneligibleView( + reason: .siteSettingsNotAvailable, + onRefresh: {} + ) + } } #Preview("Feature switch sync failure") { - POSIneligibleView( - reason: .featureSwitchSyncFailure, - onRefresh: {} - ) + if #available(iOS 17.0, *) { + POSIneligibleView( + reason: .featureSwitchSyncFailure, + onRefresh: {} + ) + } } #Preview("Unsupported WooCommerce version") { - POSIneligibleView( - reason: .unsupportedWooCommerceVersion, - onRefresh: {} - ) + if #available(iOS 17.0, *) { + POSIneligibleView( + reason: .unsupportedWooCommerceVersion, + onRefresh: {} + ) + } } #endif From 35374aad3b648085ed3187e8ea0188c3c4d47fe1 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 2 Jul 2025 14:27:45 -0400 Subject: [PATCH 06/12] Fix lint warnings from long lines. --- WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index 2b5d79e40c8..0f7ce2ea899 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -95,7 +95,8 @@ struct POSIneligibleView: View { comment: "Suggestion for missing WooCommerce plugin: install plugin") case .featureSwitchDisabled: return NSLocalizedString("pos.ineligible.suggestion.featureSwitchDisabled", - value: "The POS core feature must be enabled to proceed. Please enable the POS feature from your WordPress admin under WooCommerce settings > Advanced > Features.", + value: "The POS core feature must be enabled to proceed. " + + "Please enable the POS feature from your WordPress admin under WooCommerce settings > Advanced > Features.", comment: "Suggestion for disabled feature switch: enable feature in WooCommerce settings") case .featureSwitchSyncFailure: return NSLocalizedString("pos.ineligible.suggestion.featureSwitchSyncFailure", @@ -116,7 +117,8 @@ struct POSIneligibleView: View { let formattedCurrencyList = ListFormatter.localizedString(byJoining: currencyList) let format = NSLocalizedString( "pos.ineligible.suggestion.unsupportedCurrency", - value: "The POS system is not available for your store’s currency. It currently supports only %1$@. Please check your store currency settings or contact support for assistance.", + value: "The POS system is not available for your store’s currency. It currently supports only %1$@. " + + "Please check your store currency settings or contact support for assistance.", comment: "Suggestion for unsupported currency with list of supported currencies. " + "%1$@ is a placeholder for the localized list of supported currency codes." ) From 5989d55e193ef39c6c9f135df1dc183fd938a820 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 2 Jul 2025 14:33:05 -0400 Subject: [PATCH 07/12] DI minimum WC version to `POSIneligibleReason.unsupportedWooCommerceVersion` case. --- .../Classes/POS/TabBar/POSIneligibleView.swift | 13 +++++++------ .../Settings/POS/POSTabEligibilityChecker.swift | 4 ++-- .../POS/POSTabEligibilityCheckerTests.swift | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index 0f7ce2ea899..cdb82699f2c 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -83,12 +83,13 @@ struct POSIneligibleView: View { return NSLocalizedString("pos.ineligible.suggestion.unsupportedIOSVersion", value: "The POS system requires iOS 17 or later. Please update your device to iOS 17+ to use this feature.", comment: "Suggestion for unsupported iOS version: update iOS") - case .unsupportedWooCommerceVersion: - // TODO: DI min WC version - return NSLocalizedString("pos.ineligible.suggestion.unsupportedWooCommerceVersion", + case let .unsupportedWooCommerceVersion(minimumVersion): + let format = NSLocalizedString("pos.ineligible.suggestion.unsupportedWooCommerceVersion", value: "Your WooCommerce version is not supported. " + - "The POS system requires WooCommerce version ### or above. Please update WooCommerce to the latest version.", - comment: "Suggestion for unsupported WooCommerce version: update plugin") + "The POS system requires WooCommerce version %1$@ or above. Please update WooCommerce to the latest version.", + comment: "Suggestion for unsupported WooCommerce version: update plugin. " + + "%1$@ is a placeholder for the minimum required version.") + return String.localizedStringWithFormat(format, minimumVersion) case .wooCommercePluginNotFound: return NSLocalizedString("pos.ineligible.suggestion.wooCommercePluginNotFound", value: "Install and activate the WooCommerce plugin from your WordPress admin.", @@ -248,7 +249,7 @@ private extension POSIneligibleView { #Preview("Unsupported WooCommerce version") { if #available(iOS 17.0, *) { POSIneligibleView( - reason: .unsupportedWooCommerceVersion, + reason: .unsupportedWooCommerceVersion(minimumVersion: "9.6.0"), onRefresh: {} ) } diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift index b89b0501fec..653d977aa01 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Settings/POS/POSTabEligibilityChecker.swift @@ -19,7 +19,7 @@ import class Yosemite.PluginsService enum POSIneligibleReason: Equatable { case notTablet case unsupportedIOSVersion - case unsupportedWooCommerceVersion + case unsupportedWooCommerceVersion(minimumVersion: String) case siteSettingsNotAvailable case wooCommercePluginNotFound case featureFlagDisabled @@ -178,7 +178,7 @@ private extension POSTabEligibilityChecker { guard VersionHelpers.isVersionSupported(version: wcPlugin.version, minimumRequired: Constants.wcPluginMinimumVersion) else { - return .ineligible(reason: .unsupportedWooCommerceVersion) + return .ineligible(reason: .unsupportedWooCommerceVersion(minimumVersion: Constants.wcPluginMinimumVersion)) } // For versions below 10.0.0, the feature is enabled by default. diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift index 9f30e96ac4e..9706b1319b2 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Settings/POS/POSTabEligibilityCheckerTests.swift @@ -184,7 +184,7 @@ struct POSTabEligibilityCheckerTests { let result = await checker.checkEligibility() // Then - #expect(result == .ineligible(reason: .unsupportedWooCommerceVersion)) + #expect(result == .ineligible(reason: .unsupportedWooCommerceVersion(minimumVersion: "9.6.0-beta"))) } @Test(arguments: [true, false]) From 9f7fe65cc28c9b774ea7c70e4bfa7f7fbc0ddfb0 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Wed, 2 Jul 2025 14:40:36 -0400 Subject: [PATCH 08/12] Polish element widths based on design. --- .../POS/TabBar/POSIneligibleView.swift | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index cdb82699f2c..8a76f531f1e 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -22,18 +22,20 @@ struct POSIneligibleView: View { Spacer() .frame(height: POSSpacing.medium) - Text(Localization.title) - .font(POSFontStyle.posHeadingBold.font()) - .multilineTextAlignment(.center) - .foregroundColor(Color.posOnSurface) + VStack(spacing: POSSpacing.small) { + Text(Localization.title) + .font(POSFontStyle.posHeadingBold.font()) + .multilineTextAlignment(.center) + .foregroundColor(Color.posOnSurface) - Spacer() - .frame(height: POSSpacing.small) - - Text(suggestionText) - .font(POSFontStyle.posBodyLargeRegular().font()) - .multilineTextAlignment(.center) - .foregroundColor(Color.posOnSurface) + Text(suggestionText) + .font(POSFontStyle.posBodyLargeRegular().font()) + .multilineTextAlignment(.center) + .foregroundColor(Color.posOnSurface) + } + .containerRelativeFrame(.horizontal) { length, _ in + length * 0.5 + } Spacer() .frame(height: POSSpacing.large) @@ -64,7 +66,7 @@ struct POSIneligibleView: View { .buttonStyle(POSOutlinedButtonStyle(size: .normal)) } .containerRelativeFrame(.horizontal) { length, _ in - length * 0.5 + length * 0.5 - 132 } } From 33eb215349b2b9804834183e013043f511cea4df Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Thu, 3 Jul 2025 09:27:13 -0400 Subject: [PATCH 09/12] Use POSSpacing for 0 spacing for consistency. --- WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index 8a76f531f1e..d3b0573a5dd 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -10,10 +10,10 @@ struct POSIneligibleView: View { @State private var isLoading: Bool = false var body: some View { - VStack(spacing: 0) { + VStack(spacing: POSSpacing.none) { Spacer() - VStack(alignment: .center, spacing: 0) { + VStack(alignment: .center, spacing: POSSpacing.none) { Image(PointOfSaleAssets.exclamationMark.imageName) .resizable() .frame(width: POSErrorAndAlertIconSize.large.dimension, From f95efc572c6fa4688c1f56b1b03c17050c87d76b Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Thu, 3 Jul 2025 09:27:34 -0400 Subject: [PATCH 10/12] Use DDLogError for error logging instead of print. --- WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index d3b0573a5dd..c070f33fc21 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -49,7 +49,7 @@ struct POSIneligibleView: View { isLoading = false } catch { // TODO: WOOMOB-720 - handle error if needed, e.g., show an error message - print("Error refreshing eligibility: \(error)") + DDLogError("Error refreshing eligibility: \(error)") isLoading = false } } From 8ae5ba5dde69741537305bdf3a84f9b7c53e34fa Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Thu, 3 Jul 2025 09:27:51 -0400 Subject: [PATCH 11/12] Copy polishes from code review suggestion. --- WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index c070f33fc21..e4448b7dad5 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -83,7 +83,7 @@ struct POSIneligibleView: View { comment: "Suggestion for not tablet: use iPad") case .unsupportedIOSVersion: return NSLocalizedString("pos.ineligible.suggestion.unsupportedIOSVersion", - value: "The POS system requires iOS 17 or later. Please update your device to iOS 17+ to use this feature.", + value: "Point of Sale requires iOS 17 or later. Please update your device to iOS 17+ to use this feature.", comment: "Suggestion for unsupported iOS version: update iOS") case let .unsupportedWooCommerceVersion(minimumVersion): let format = NSLocalizedString("pos.ineligible.suggestion.unsupportedWooCommerceVersion", @@ -98,7 +98,7 @@ struct POSIneligibleView: View { comment: "Suggestion for missing WooCommerce plugin: install plugin") case .featureSwitchDisabled: return NSLocalizedString("pos.ineligible.suggestion.featureSwitchDisabled", - value: "The POS core feature must be enabled to proceed. " + + value: "Point of Sale must be enabled to proceed. " + "Please enable the POS feature from your WordPress admin under WooCommerce settings > Advanced > Features.", comment: "Suggestion for disabled feature switch: enable feature in WooCommerce settings") case .featureSwitchSyncFailure: From 156eefe48309642d58685316be1e62e1f70b7c43 Mon Sep 17 00:00:00 2001 From: Jaclyn Chen Date: Thu, 3 Jul 2025 09:48:10 -0400 Subject: [PATCH 12/12] Give both text and buttons width at least 300px based on 320px minimum device width for current iOS devices. --- WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift index e4448b7dad5..e91cac78c45 100644 --- a/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift +++ b/WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift @@ -34,7 +34,7 @@ struct POSIneligibleView: View { .foregroundColor(Color.posOnSurface) } .containerRelativeFrame(.horizontal) { length, _ in - length * 0.5 + max(length * 0.5, 300) } Spacer() @@ -66,7 +66,7 @@ struct POSIneligibleView: View { .buttonStyle(POSOutlinedButtonStyle(size: .normal)) } .containerRelativeFrame(.horizontal) { length, _ in - length * 0.5 - 132 + max(length * 0.5 - 132, 300) } }