From 059bcd9d791e96738cba08bf7bece11178ae32fb Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Mon, 25 Aug 2025 14:38:14 +0700 Subject: [PATCH 01/29] make PointOfSaleSettingsService --- .../Models/PointOfSaleSettingsService.swift | 49 ++++++++++ .../PointOfSaleSettingsStoreDetailView.swift | 89 +++++++++++++++++-- .../WooCommerce.xcodeproj/project.pbxproj | 4 + 3 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift new file mode 100644 index 00000000000..1a5c6f72280 --- /dev/null +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift @@ -0,0 +1,49 @@ +import Yosemite + +final class PointOfSaleSettingsService { + let receiptStoreName: String + let receiptStoreAddress: String + let receiptStorePhone: String + let receiptStoreEmail: String + let receiptRefundReturnsPolicy: String + + var storeName: String { + guard let site = ServiceLocator.stores.sessionManager.defaultSite else { + return "Not set" + } + return site.name + } + + var storeAddress: String { + SiteAddress().address + } + + var storeEmail: String { + "Not set" // TBD + } + + static let empty = PointOfSaleSettingsService(from: []) + + init(from siteSettings: [SiteSetting]) { + self.receiptStoreName = siteSettings.first { $0.settingID == "woocommerce_pos_store_name" }?.value ?? "Not set" + self.receiptStoreAddress = siteSettings.first { $0.settingID == "woocommerce_pos_store_address" }?.value ?? "Not set" + self.receiptStorePhone = siteSettings.first { $0.settingID == "woocommerce_pos_store_phone" }?.value ?? "Not set" + self.receiptStoreEmail = siteSettings.first { $0.settingID == "woocommerce_pos_store_email" }?.value ?? "Not set" + self.receiptRefundReturnsPolicy = siteSettings.first { $0.settingID == "woocommerce_pos_refund_returns_policy" }?.value ?? "Not set" + } + + @MainActor + func isPluginSupported(_ plugin: Plugin, minimumVersion: String) async -> Bool { + let siteID = ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 + let storageManager = ServiceLocator.storageManager + let pluginsService = PluginsService(storageManager: storageManager) + guard let systemPlugin = pluginsService.loadPluginInStorage(siteID: siteID, plugin: plugin, isActive: true), + systemPlugin.active else { + return false + } + + let isSupported = VersionHelpers.isVersionSupported(version: systemPlugin.version, + minimumRequired: minimumVersion) + return isSupported + } +} diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index 2192d786ce3..4cd084a85b5 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -1,16 +1,95 @@ import SwiftUI +import Yosemite struct PointOfSaleSettingsStoreDetailView: View { + @Environment(\.dismiss) private var dismiss + @State private var shouldShowReceiptInformation = true + @State private var posSettingsService: PointOfSaleSettingsService = .empty + @State private var isLoadingSettings = true + var body: some View { NavigationStack { VStack(alignment: .leading) { - Text("Store Settings") - .font(.title2) - Text("Store-related configuration") - .font(.caption) - .foregroundStyle(.secondary) + Group { + Text("Store Information") + .font(.title2) + + Text("Store name") + Text(posSettingsService.storeName) + .font(.caption) + .foregroundStyle(.secondary) + + Text("Address") + Text(posSettingsService.storeAddress) + .font(.caption) + .foregroundStyle(.secondary) + + Text("Email") + Text(posSettingsService.storeEmail) + .font(.caption) + .foregroundStyle(.secondary) + } + + Group { + Spacer() + Text("Receipt Information") + .font(.title2) + Text("Store name") + Text(isLoadingSettings ? "Loading..." : posSettingsService.receiptStoreName) + .font(.caption) + .foregroundStyle(.secondary) + + Text("Physical address") + Text(isLoadingSettings ? "Loading..." : posSettingsService.receiptStoreAddress) + .font(.caption) + .foregroundStyle(.secondary) + + Text("Phone number") + Text(isLoadingSettings ? "Loading..." : posSettingsService.receiptStorePhone) + .font(.caption) + .foregroundStyle(.secondary) + + Text("Email") + Text(isLoadingSettings ? "Loading..." : posSettingsService.receiptStoreEmail) + .font(.caption) + .foregroundStyle(.secondary) + + Text("Refund & Returns Policy") + Text(isLoadingSettings ? "Loading..." : posSettingsService.receiptRefundReturnsPolicy) + .font(.caption) + .foregroundStyle(.secondary) + + } + .renderedIf(shouldShowReceiptInformation) } .padding() + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("Done") { dismiss() } + } + } + } + .task { + shouldShowReceiptInformation = await posSettingsService.isPluginSupported(.wooCommerce, minimumVersion: "10.0") + } + .task { + let siteID = ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 + let action = SettingAction.retrievePointOfSaleSettings(siteID: siteID) { result in + switch result { + case .success(let siteSettings): + self.posSettingsService = PointOfSaleSettingsService(from: siteSettings) + self.isLoadingSettings = false + case .failure(let error): + DDLogError("Failed to load POS settings: \(error)") + self.posSettingsService = .empty + self.isLoadingSettings = false + } + } + ServiceLocator.stores.dispatch(action) } } } + +#Preview { + PointOfSaleSettingsStoreDetailView() +} diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 767780d07d5..118fac29982 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -1648,6 +1648,7 @@ 68E952CC287536010095A23D /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E952CB287536010095A23D /* SafariView.swift */; }; 68E952D0287587BF0095A23D /* CardReaderManualRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E952CF287587BF0095A23D /* CardReaderManualRowView.swift */; }; 68E952D22875A44B0095A23D /* CardReaderType+Manual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E952D12875A44B0095A23D /* CardReaderType+Manual.swift */; }; + 68E9F7012E5C499200D45747 /* PointOfSaleSettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E9F7002E5C499000D45747 /* PointOfSaleSettingsService.swift */; }; 68ED2BD62ADD2C8C00ECA88D /* LineDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68ED2BD52ADD2C8C00ECA88D /* LineDetailView.swift */; }; 68F151E12C0DA7910082AEC8 /* Cart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68F151E02C0DA7910082AEC8 /* Cart.swift */; }; 68F68A502D6730E200BB9568 /* POSCollectOrderPaymentAnalyticsTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68F68A4F2D6730DF00BB9568 /* POSCollectOrderPaymentAnalyticsTracking.swift */; }; @@ -4824,6 +4825,7 @@ 68E952CB287536010095A23D /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; 68E952CF287587BF0095A23D /* CardReaderManualRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderManualRowView.swift; sourceTree = ""; }; 68E952D12875A44B0095A23D /* CardReaderType+Manual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardReaderType+Manual.swift"; sourceTree = ""; }; + 68E9F7002E5C499000D45747 /* PointOfSaleSettingsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleSettingsService.swift; sourceTree = ""; }; 68ED2BD52ADD2C8C00ECA88D /* LineDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineDetailView.swift; sourceTree = ""; }; 68F151E02C0DA7910082AEC8 /* Cart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cart.swift; sourceTree = ""; }; 68F68A4F2D6730DF00BB9568 /* POSCollectOrderPaymentAnalyticsTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSCollectOrderPaymentAnalyticsTracking.swift; sourceTree = ""; }; @@ -9966,6 +9968,7 @@ children = ( 01B3A1F12DB6D48800286B7F /* ItemListType.swift */, 20FCBCDC2CE223340082DCA3 /* PointOfSaleAggregateModel.swift */, + 68E9F7002E5C499000D45747 /* PointOfSaleSettingsService.swift */, 209ECA802DB8FC280089F3D2 /* PointOfSaleViewStateCoordinator.swift */, 20C6E7502CDE4AEA00CD124C /* ItemListState.swift */, 20F7B12C2D12C7B900C08193 /* ItemsContainerState.swift */, @@ -15576,6 +15579,7 @@ CE21B3D720FE669A00A259D5 /* BasicTableViewCell.swift in Sources */, 451A04EA2386D28300E368C9 /* ProductImagesHeaderViewModel.swift in Sources */, 02307924258731B2008EADEE /* PrintShippingLabelViewModel.swift in Sources */, + 68E9F7012E5C499200D45747 /* PointOfSaleSettingsService.swift in Sources */, D843D5D92248EE91001BFA55 /* ManualTrackingViewModel.swift in Sources */, 20886D3D2D96E0F900F7AE03 /* PointOfSaleCardPresentPaymentConnectingFailedLocationRequiredAlertViewModel.swift in Sources */, 204CB8102C10BB88000C9773 /* CardPresentPaymentPreviewService.swift in Sources */, From 433a5d8c5efd4f00733e20446c818d2152946d85 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Mon, 25 Aug 2025 15:06:13 +0700 Subject: [PATCH 02/29] update init --- .../Models/PointOfSaleSettingsService.swift | 51 +++++++++++++------ .../PointOfSaleSettingsStoreDetailView.swift | 32 +++--------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift index 1a5c6f72280..fa3045599a5 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift @@ -1,11 +1,13 @@ import Yosemite +import Observation -final class PointOfSaleSettingsService { - let receiptStoreName: String - let receiptStoreAddress: String - let receiptStorePhone: String - let receiptStoreEmail: String - let receiptRefundReturnsPolicy: String +@Observable final class PointOfSaleSettingsService { + private(set) var receiptStoreName: String = "Not set" + private(set) var receiptStoreAddress: String = "Not set" + private(set) var receiptStorePhone: String = "Not set" + private(set) var receiptStoreEmail: String = "Not set" + private(set) var receiptRefundReturnsPolicy: String = "Not set" + private(set) var isLoading: Bool = false var storeName: String { guard let site = ServiceLocator.stores.sessionManager.defaultSite else { @@ -18,23 +20,39 @@ final class PointOfSaleSettingsService { SiteAddress().address } - var storeEmail: String { - "Not set" // TBD + private var siteID: Int64 { + ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 } - static let empty = PointOfSaleSettingsService(from: []) + static let empty = PointOfSaleSettingsService() - init(from siteSettings: [SiteSetting]) { - self.receiptStoreName = siteSettings.first { $0.settingID == "woocommerce_pos_store_name" }?.value ?? "Not set" - self.receiptStoreAddress = siteSettings.first { $0.settingID == "woocommerce_pos_store_address" }?.value ?? "Not set" - self.receiptStorePhone = siteSettings.first { $0.settingID == "woocommerce_pos_store_phone" }?.value ?? "Not set" - self.receiptStoreEmail = siteSettings.first { $0.settingID == "woocommerce_pos_store_email" }?.value ?? "Not set" - self.receiptRefundReturnsPolicy = siteSettings.first { $0.settingID == "woocommerce_pos_refund_returns_policy" }?.value ?? "Not set" + init() { } + + @MainActor + func loadSettings() async { + isLoading = true + + await withCheckedContinuation { (continuation: CheckedContinuation) in + let action = SettingAction.retrievePointOfSaleSettings(siteID: siteID) { [weak self] result in + switch result { + case .success(let siteSettings): + self?.receiptStoreName = siteSettings.first { $0.settingID == "woocommerce_pos_store_name" }?.value ?? "Not set" + self?.receiptStoreAddress = siteSettings.first { $0.settingID == "woocommerce_pos_store_address" }?.value ?? "Not set" + self?.receiptStorePhone = siteSettings.first { $0.settingID == "woocommerce_pos_store_phone" }?.value ?? "Not set" + self?.receiptStoreEmail = siteSettings.first { $0.settingID == "woocommerce_pos_store_email" }?.value ?? "Not set" + self?.receiptRefundReturnsPolicy = siteSettings.first { $0.settingID == "woocommerce_pos_refund_returns_policy" }?.value ?? "Not set" + case .failure(let error): + DDLogError("Failed to load POS settings: \(error)") + } + self?.isLoading = false + continuation.resume() + } + ServiceLocator.stores.dispatch(action) + } } @MainActor func isPluginSupported(_ plugin: Plugin, minimumVersion: String) async -> Bool { - let siteID = ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 let storageManager = ServiceLocator.storageManager let pluginsService = PluginsService(storageManager: storageManager) guard let systemPlugin = pluginsService.loadPluginInStorage(siteID: siteID, plugin: plugin, isActive: true), @@ -46,4 +64,5 @@ final class PointOfSaleSettingsService { minimumRequired: minimumVersion) return isSupported } + } diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index 4cd084a85b5..86a0506c51e 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -4,8 +4,7 @@ import Yosemite struct PointOfSaleSettingsStoreDetailView: View { @Environment(\.dismiss) private var dismiss @State private var shouldShowReceiptInformation = true - @State private var posSettingsService: PointOfSaleSettingsService = .empty - @State private var isLoadingSettings = true + @State private var posSettingsService = PointOfSaleSettingsService() var body: some View { NavigationStack { @@ -23,11 +22,6 @@ struct PointOfSaleSettingsStoreDetailView: View { Text(posSettingsService.storeAddress) .font(.caption) .foregroundStyle(.secondary) - - Text("Email") - Text(posSettingsService.storeEmail) - .font(.caption) - .foregroundStyle(.secondary) } Group { @@ -35,27 +29,27 @@ struct PointOfSaleSettingsStoreDetailView: View { Text("Receipt Information") .font(.title2) Text("Store name") - Text(isLoadingSettings ? "Loading..." : posSettingsService.receiptStoreName) + Text(posSettingsService.isLoading ? "Loading..." : posSettingsService.receiptStoreName) .font(.caption) .foregroundStyle(.secondary) Text("Physical address") - Text(isLoadingSettings ? "Loading..." : posSettingsService.receiptStoreAddress) + Text(posSettingsService.isLoading ? "Loading..." : posSettingsService.receiptStoreAddress) .font(.caption) .foregroundStyle(.secondary) Text("Phone number") - Text(isLoadingSettings ? "Loading..." : posSettingsService.receiptStorePhone) + Text(posSettingsService.isLoading ? "Loading..." : posSettingsService.receiptStorePhone) .font(.caption) .foregroundStyle(.secondary) Text("Email") - Text(isLoadingSettings ? "Loading..." : posSettingsService.receiptStoreEmail) + Text(posSettingsService.isLoading ? "Loading..." : posSettingsService.receiptStoreEmail) .font(.caption) .foregroundStyle(.secondary) Text("Refund & Returns Policy") - Text(isLoadingSettings ? "Loading..." : posSettingsService.receiptRefundReturnsPolicy) + Text(posSettingsService.isLoading ? "Loading..." : posSettingsService.receiptRefundReturnsPolicy) .font(.caption) .foregroundStyle(.secondary) @@ -73,19 +67,7 @@ struct PointOfSaleSettingsStoreDetailView: View { shouldShowReceiptInformation = await posSettingsService.isPluginSupported(.wooCommerce, minimumVersion: "10.0") } .task { - let siteID = ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 - let action = SettingAction.retrievePointOfSaleSettings(siteID: siteID) { result in - switch result { - case .success(let siteSettings): - self.posSettingsService = PointOfSaleSettingsService(from: siteSettings) - self.isLoadingSettings = false - case .failure(let error): - DDLogError("Failed to load POS settings: \(error)") - self.posSettingsService = .empty - self.isLoadingSettings = false - } - } - ServiceLocator.stores.dispatch(action) + await posSettingsService.loadSettings() } } } From 4713200efced6313857c87d3b5d1de8d9bbe96a7 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Mon, 25 Aug 2025 15:25:10 +0700 Subject: [PATCH 03/29] move version check to service --- .../POS/Models/PointOfSaleSettingsService.swift | 3 +++ .../Settings/PointOfSaleSettingsStoreDetailView.swift | 10 +++------- .../Settings/PointOfSaleSettingsView.swift | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift index fa3045599a5..ae0a89312d5 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift @@ -8,6 +8,7 @@ import Observation private(set) var receiptStoreEmail: String = "Not set" private(set) var receiptRefundReturnsPolicy: String = "Not set" private(set) var isLoading: Bool = false + private(set) var shouldShowReceiptInformation: Bool = false var storeName: String { guard let site = ServiceLocator.stores.sessionManager.defaultSite else { @@ -32,6 +33,8 @@ import Observation func loadSettings() async { isLoading = true + shouldShowReceiptInformation = await isPluginSupported(.wooCommerce, minimumVersion: "10.0") + await withCheckedContinuation { (continuation: CheckedContinuation) in let action = SettingAction.retrievePointOfSaleSettings(siteID: siteID) { [weak self] result in switch result { diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index 86a0506c51e..47e65c2a416 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -3,8 +3,7 @@ import Yosemite struct PointOfSaleSettingsStoreDetailView: View { @Environment(\.dismiss) private var dismiss - @State private var shouldShowReceiptInformation = true - @State private var posSettingsService = PointOfSaleSettingsService() + let posSettingsService: PointOfSaleSettingsService var body: some View { NavigationStack { @@ -54,7 +53,7 @@ struct PointOfSaleSettingsStoreDetailView: View { .foregroundStyle(.secondary) } - .renderedIf(shouldShowReceiptInformation) + .renderedIf(posSettingsService.shouldShowReceiptInformation) } .padding() .toolbar { @@ -63,9 +62,6 @@ struct PointOfSaleSettingsStoreDetailView: View { } } } - .task { - shouldShowReceiptInformation = await posSettingsService.isPluginSupported(.wooCommerce, minimumVersion: "10.0") - } .task { await posSettingsService.loadSettings() } @@ -73,5 +69,5 @@ struct PointOfSaleSettingsStoreDetailView: View { } #Preview { - PointOfSaleSettingsStoreDetailView() + PointOfSaleSettingsStoreDetailView(posSettingsService: PointOfSaleSettingsService()) } diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift index e653db70344..6ff8d7552f2 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift @@ -66,7 +66,7 @@ struct PointOfSaleSettingsView: View { Group { switch selection { case .store: - PointOfSaleSettingsStoreDetailView() + PointOfSaleSettingsStoreDetailView(posSettingsService: PointOfSaleSettingsService()) case .hardware: PointOfSaleSettingsHardwareDetailView() case .help: From af3b76e620778385b35002e2e18cd3e628d6c96b Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Mon, 25 Aug 2025 15:28:56 +0700 Subject: [PATCH 04/29] make settingValueView --- .../PointOfSaleSettingsStoreDetailView.swift | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index 47e65c2a416..8a0afc8e94b 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -28,29 +28,19 @@ struct PointOfSaleSettingsStoreDetailView: View { Text("Receipt Information") .font(.title2) Text("Store name") - Text(posSettingsService.isLoading ? "Loading..." : posSettingsService.receiptStoreName) - .font(.caption) - .foregroundStyle(.secondary) + settingValueView(for: posSettingsService.receiptStoreName) Text("Physical address") - Text(posSettingsService.isLoading ? "Loading..." : posSettingsService.receiptStoreAddress) - .font(.caption) - .foregroundStyle(.secondary) + settingValueView(for: posSettingsService.receiptStoreAddress) Text("Phone number") - Text(posSettingsService.isLoading ? "Loading..." : posSettingsService.receiptStorePhone) - .font(.caption) - .foregroundStyle(.secondary) + settingValueView(for: posSettingsService.receiptStorePhone) Text("Email") - Text(posSettingsService.isLoading ? "Loading..." : posSettingsService.receiptStoreEmail) - .font(.caption) - .foregroundStyle(.secondary) + settingValueView(for: posSettingsService.receiptStoreEmail) Text("Refund & Returns Policy") - Text(posSettingsService.isLoading ? "Loading..." : posSettingsService.receiptRefundReturnsPolicy) - .font(.caption) - .foregroundStyle(.secondary) + settingValueView(for: posSettingsService.receiptRefundReturnsPolicy) } .renderedIf(posSettingsService.shouldShowReceiptInformation) @@ -66,6 +56,18 @@ struct PointOfSaleSettingsStoreDetailView: View { await posSettingsService.loadSettings() } } + + @ViewBuilder + private func settingValueView(for value: String) -> some View { + if posSettingsService.isLoading { + ProgressView() + .controlSize(.small) + } else { + Text(value) + .font(.caption) + .foregroundStyle(.secondary) + } + } } #Preview { From 42014165ceaef73ac7e8b06262f5f46c72145c79 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Mon, 25 Aug 2025 15:41:06 +0700 Subject: [PATCH 05/29] early return if version not supported --- .../Models/PointOfSaleSettingsService.swift | 30 +++++++++++-------- .../PointOfSaleSettingsStoreDetailView.swift | 6 ++-- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift index ae0a89312d5..c23e25e3137 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift @@ -2,11 +2,11 @@ import Yosemite import Observation @Observable final class PointOfSaleSettingsService { - private(set) var receiptStoreName: String = "Not set" - private(set) var receiptStoreAddress: String = "Not set" - private(set) var receiptStorePhone: String = "Not set" - private(set) var receiptStoreEmail: String = "Not set" - private(set) var receiptRefundReturnsPolicy: String = "Not set" + private(set) var receiptStoreName: String? + private(set) var receiptStoreAddress: String? + private(set) var receiptStorePhone: String? + private(set) var receiptStoreEmail: String? + private(set) var receiptRefundReturnsPolicy: String? private(set) var isLoading: Bool = false private(set) var shouldShowReceiptInformation: Bool = false @@ -30,24 +30,30 @@ import Observation init() { } @MainActor - func loadSettings() async { + func retrievePOSReceiptSettings() async { isLoading = true shouldShowReceiptInformation = await isPluginSupported(.wooCommerce, minimumVersion: "10.0") + guard shouldShowReceiptInformation else { + isLoading = false + return + } + await withCheckedContinuation { (continuation: CheckedContinuation) in let action = SettingAction.retrievePointOfSaleSettings(siteID: siteID) { [weak self] result in + guard let self else { return } switch result { case .success(let siteSettings): - self?.receiptStoreName = siteSettings.first { $0.settingID == "woocommerce_pos_store_name" }?.value ?? "Not set" - self?.receiptStoreAddress = siteSettings.first { $0.settingID == "woocommerce_pos_store_address" }?.value ?? "Not set" - self?.receiptStorePhone = siteSettings.first { $0.settingID == "woocommerce_pos_store_phone" }?.value ?? "Not set" - self?.receiptStoreEmail = siteSettings.first { $0.settingID == "woocommerce_pos_store_email" }?.value ?? "Not set" - self?.receiptRefundReturnsPolicy = siteSettings.first { $0.settingID == "woocommerce_pos_refund_returns_policy" }?.value ?? "Not set" + receiptStoreName = siteSettings.first { $0.settingID == "woocommerce_pos_store_name" }?.value + receiptStoreAddress = siteSettings.first { $0.settingID == "woocommerce_pos_store_address" }?.value + receiptStorePhone = siteSettings.first { $0.settingID == "woocommerce_pos_store_phone" }?.value + receiptStoreEmail = siteSettings.first { $0.settingID == "woocommerce_pos_store_email" }?.value + receiptRefundReturnsPolicy = siteSettings.first { $0.settingID == "woocommerce_pos_refund_returns_policy" }?.value case .failure(let error): DDLogError("Failed to load POS settings: \(error)") } - self?.isLoading = false + isLoading = false continuation.resume() } ServiceLocator.stores.dispatch(action) diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index 8a0afc8e94b..97428a1b26a 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -53,17 +53,17 @@ struct PointOfSaleSettingsStoreDetailView: View { } } .task { - await posSettingsService.loadSettings() + await posSettingsService.retrievePOSReceiptSettings() } } @ViewBuilder - private func settingValueView(for value: String) -> some View { + private func settingValueView(for value: String?) -> some View { if posSettingsService.isLoading { ProgressView() .controlSize(.small) } else { - Text(value) + Text(value ?? "Not set") .font(.caption) .foregroundStyle(.secondary) } From 7b66d8fc72999f85d2175ff92a6b5005d3b8cedb Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Mon, 25 Aug 2025 15:46:23 +0700 Subject: [PATCH 06/29] cleanup --- .../Models/PointOfSaleSettingsService.swift | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift index c23e25e3137..8f48bd6a336 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift @@ -10,6 +10,10 @@ import Observation private(set) var isLoading: Bool = false private(set) var shouldShowReceiptInformation: Bool = false + private var siteID: Int64 { + ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 + } + var storeName: String { guard let site = ServiceLocator.stores.sessionManager.defaultSite else { return "Not set" @@ -21,14 +25,6 @@ import Observation SiteAddress().address } - private var siteID: Int64 { - ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 - } - - static let empty = PointOfSaleSettingsService() - - init() { } - @MainActor func retrievePOSReceiptSettings() async { isLoading = true @@ -45,11 +41,7 @@ import Observation guard let self else { return } switch result { case .success(let siteSettings): - receiptStoreName = siteSettings.first { $0.settingID == "woocommerce_pos_store_name" }?.value - receiptStoreAddress = siteSettings.first { $0.settingID == "woocommerce_pos_store_address" }?.value - receiptStorePhone = siteSettings.first { $0.settingID == "woocommerce_pos_store_phone" }?.value - receiptStoreEmail = siteSettings.first { $0.settingID == "woocommerce_pos_store_email" }?.value - receiptRefundReturnsPolicy = siteSettings.first { $0.settingID == "woocommerce_pos_refund_returns_policy" }?.value + updateReceiptSettings(from: siteSettings) case .failure(let error): DDLogError("Failed to load POS settings: \(error)") } @@ -61,7 +53,7 @@ import Observation } @MainActor - func isPluginSupported(_ plugin: Plugin, minimumVersion: String) async -> Bool { + private func isPluginSupported(_ plugin: Plugin, minimumVersion: String) async -> Bool { let storageManager = ServiceLocator.storageManager let pluginsService = PluginsService(storageManager: storageManager) guard let systemPlugin = pluginsService.loadPluginInStorage(siteID: siteID, plugin: plugin, isActive: true), @@ -74,4 +66,16 @@ import Observation return isSupported } + private func updateReceiptSettings(from siteSettings: [SiteSetting]) { + receiptStoreName = settingValue(from: siteSettings, settingID: "woocommerce_pos_store_name") + receiptStoreAddress = settingValue(from: siteSettings, settingID: "woocommerce_pos_store_address") + receiptStorePhone = settingValue(from: siteSettings, settingID: "woocommerce_pos_store_phone") + receiptStoreEmail = settingValue(from: siteSettings, settingID: "woocommerce_pos_store_email") + receiptRefundReturnsPolicy = settingValue(from: siteSettings, settingID: "woocommerce_pos_refund_returns_policy") + } + + private func settingValue(from siteSettings: [SiteSetting], settingID: String) -> String? { + let value = siteSettings.first { $0.settingID == settingID }?.value + return value?.isEmpty == true ? nil : value + } } From 46035e482f6e15a8e9a02d55940825128de2a2b9 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Mon, 25 Aug 2025 15:55:14 +0700 Subject: [PATCH 07/29] update NSLocalizedStrings --- .../PointOfSaleSettingsStoreDetailView.swift | 90 +++++++++++++++---- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index 97428a1b26a..57de149908d 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -2,22 +2,21 @@ import SwiftUI import Yosemite struct PointOfSaleSettingsStoreDetailView: View { - @Environment(\.dismiss) private var dismiss let posSettingsService: PointOfSaleSettingsService var body: some View { NavigationStack { VStack(alignment: .leading) { Group { - Text("Store Information") + Text(Localization.storeInformation) .font(.title2) - Text("Store name") + Text(Localization.storeName) Text(posSettingsService.storeName) .font(.caption) .foregroundStyle(.secondary) - Text("Address") + Text(Localization.address) Text(posSettingsService.storeAddress) .font(.caption) .foregroundStyle(.secondary) @@ -25,32 +24,27 @@ struct PointOfSaleSettingsStoreDetailView: View { Group { Spacer() - Text("Receipt Information") + Text(Localization.receiptInformation) .font(.title2) - Text("Store name") + Text(Localization.receiptStoreName) settingValueView(for: posSettingsService.receiptStoreName) - Text("Physical address") + Text(Localization.physicalAddress) settingValueView(for: posSettingsService.receiptStoreAddress) - Text("Phone number") + Text(Localization.phoneNumber) settingValueView(for: posSettingsService.receiptStorePhone) - Text("Email") + Text(Localization.email) settingValueView(for: posSettingsService.receiptStoreEmail) - Text("Refund & Returns Policy") + Text(Localization.refundReturnsPolicy) settingValueView(for: posSettingsService.receiptRefundReturnsPolicy) } .renderedIf(posSettingsService.shouldShowReceiptInformation) } .padding() - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Button("Done") { dismiss() } - } - } } .task { await posSettingsService.retrievePOSReceiptSettings() @@ -63,13 +57,77 @@ struct PointOfSaleSettingsStoreDetailView: View { ProgressView() .controlSize(.small) } else { - Text(value ?? "Not set") + Text(value ?? Localization.notSet) .font(.caption) .foregroundStyle(.secondary) } } } +private extension PointOfSaleSettingsStoreDetailView { + enum Localization { + static let notSet = NSLocalizedString( + "pointOfSaleSettingsStoreDetailView.notSet", + value: "Not set", + comment: "Text displayed on Point of Sale settings when any setting has not been provided." + ) + + static let storeInformation = NSLocalizedString( + "pointOfSaleSettingsStoreDetailView.storeInformation", + value: "Store Information", + comment: "Section title for store information in Point of Sale settings." + ) + + static let storeName = NSLocalizedString( + "pointOfSaleSettingsStoreDetailView.storeName", + value: "Store name", + comment: "Label for store name field in Point of Sale settings." + ) + + static let address = NSLocalizedString( + "pointOfSaleSettingsStoreDetailView.address", + value: "Address", + comment: "Label for address field in Point of Sale settings." + ) + + static let receiptInformation = NSLocalizedString( + "pointOfSaleSettingsStoreDetailView.receiptInformation", + value: "Receipt Information", + comment: "Section title for receipt information in Point of Sale settings." + ) + + static let receiptStoreName = NSLocalizedString( + "pointOfSaleSettingsStoreDetailView.receiptStoreName", + value: "Store name", + comment: "Label for receipt store name field in Point of Sale settings." + ) + + static let physicalAddress = NSLocalizedString( + "pointOfSaleSettingsStoreDetailView.physicalAddress", + value: "Physical address", + comment: "Label for physical address field in Point of Sale settings." + ) + + static let phoneNumber = NSLocalizedString( + "pointOfSaleSettingsStoreDetailView.phoneNumber", + value: "Phone number", + comment: "Label for phone number field in Point of Sale settings." + ) + + static let email = NSLocalizedString( + "pointOfSaleSettingsStoreDetailView.email", + value: "Email", + comment: "Label for email field in Point of Sale settings." + ) + + static let refundReturnsPolicy = NSLocalizedString( + "pointOfSaleSettingsStoreDetailView.refundReturnsPolicy", + value: "Refund & Returns Policy", + comment: "Label for refund and returns policy field in Point of Sale settings." + ) + } +} + #Preview { PointOfSaleSettingsStoreDetailView(posSettingsService: PointOfSaleSettingsService()) } From e49dad7e4570f4f381a0e2a258747d027a060953 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Mon, 25 Aug 2025 16:03:16 +0700 Subject: [PATCH 08/29] lint and POS fonts --- .../PointOfSaleSettingsStoreDetailView.swift | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index 57de149908d..5fd6aa825ff 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -9,23 +9,23 @@ struct PointOfSaleSettingsStoreDetailView: View { VStack(alignment: .leading) { Group { Text(Localization.storeInformation) - .font(.title2) + .font(.posBodyLargeRegular()) Text(Localization.storeName) Text(posSettingsService.storeName) - .font(.caption) + .font(.posBodyMediumRegular()) .foregroundStyle(.secondary) Text(Localization.address) Text(posSettingsService.storeAddress) - .font(.caption) + .font(.posBodyMediumRegular()) .foregroundStyle(.secondary) } Group { Spacer() Text(Localization.receiptInformation) - .font(.title2) + .font(.posBodyLargeRegular()) Text(Localization.receiptStoreName) settingValueView(for: posSettingsService.receiptStoreName) @@ -55,10 +55,10 @@ struct PointOfSaleSettingsStoreDetailView: View { private func settingValueView(for value: String?) -> some View { if posSettingsService.isLoading { ProgressView() - .controlSize(.small) + .font(.posBodyLargeRegular()) } else { Text(value ?? Localization.notSet) - .font(.caption) + .font(.posBodyMediumRegular()) .foregroundStyle(.secondary) } } @@ -71,55 +71,55 @@ private extension PointOfSaleSettingsStoreDetailView { value: "Not set", comment: "Text displayed on Point of Sale settings when any setting has not been provided." ) - + static let storeInformation = NSLocalizedString( "pointOfSaleSettingsStoreDetailView.storeInformation", value: "Store Information", comment: "Section title for store information in Point of Sale settings." ) - + static let storeName = NSLocalizedString( "pointOfSaleSettingsStoreDetailView.storeName", value: "Store name", comment: "Label for store name field in Point of Sale settings." ) - + static let address = NSLocalizedString( "pointOfSaleSettingsStoreDetailView.address", value: "Address", comment: "Label for address field in Point of Sale settings." ) - + static let receiptInformation = NSLocalizedString( "pointOfSaleSettingsStoreDetailView.receiptInformation", value: "Receipt Information", comment: "Section title for receipt information in Point of Sale settings." ) - + static let receiptStoreName = NSLocalizedString( "pointOfSaleSettingsStoreDetailView.receiptStoreName", value: "Store name", comment: "Label for receipt store name field in Point of Sale settings." ) - + static let physicalAddress = NSLocalizedString( "pointOfSaleSettingsStoreDetailView.physicalAddress", value: "Physical address", comment: "Label for physical address field in Point of Sale settings." ) - + static let phoneNumber = NSLocalizedString( "pointOfSaleSettingsStoreDetailView.phoneNumber", value: "Phone number", comment: "Label for phone number field in Point of Sale settings." ) - + static let email = NSLocalizedString( "pointOfSaleSettingsStoreDetailView.email", value: "Email", comment: "Label for email field in Point of Sale settings." ) - + static let refundReturnsPolicy = NSLocalizedString( "pointOfSaleSettingsStoreDetailView.refundReturnsPolicy", value: "Refund & Returns Policy", From 4bb9542f160d96e76407c16550603f1dc5e38170 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Mon, 25 Aug 2025 16:11:37 +0700 Subject: [PATCH 09/29] move setting retrieval to the parent view --- .../Settings/PointOfSaleSettingsStoreDetailView.swift | 3 --- .../POS/Presentation/Settings/PointOfSaleSettingsView.swift | 6 +++++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index 5fd6aa825ff..ebc45760788 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -46,9 +46,6 @@ struct PointOfSaleSettingsStoreDetailView: View { } .padding() } - .task { - await posSettingsService.retrievePOSReceiptSettings() - } } @ViewBuilder diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift index 6ff8d7552f2..c0f6f36f9e3 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift @@ -3,6 +3,7 @@ import SwiftUI struct PointOfSaleSettingsView: View { @Environment(\.dismiss) private var dismiss @State private var selection: SidebarNavigation? = .store + @State private var settingsService = PointOfSaleSettingsService() var body: some View { POSPageHeaderView( @@ -66,7 +67,7 @@ struct PointOfSaleSettingsView: View { Group { switch selection { case .store: - PointOfSaleSettingsStoreDetailView(posSettingsService: PointOfSaleSettingsService()) + PointOfSaleSettingsStoreDetailView(posSettingsService: settingsService) case .hardware: PointOfSaleSettingsHardwareDetailView() case .help: @@ -77,6 +78,9 @@ struct PointOfSaleSettingsView: View { } .frame(maxWidth: .infinity, maxHeight: .infinity) } + .task { + await settingsService.retrievePOSReceiptSettings() + } } } From 03834df5c569b9b52ac66f7cdf324bffd3928ed0 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Mon, 25 Aug 2025 16:20:41 +0700 Subject: [PATCH 10/29] update imports and localization --- .../Models/PointOfSaleSettingsService.swift | 18 ++++++++++++++++-- .../PointOfSaleSettingsStoreDetailView.swift | 1 - 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift index 8f48bd6a336..461e219c5a2 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift @@ -1,4 +1,8 @@ -import Yosemite +import Foundation +import struct Yosemite.SiteSetting +import enum Yosemite.SettingAction +import enum Yosemite.Plugin +import class Yosemite.PluginsService import Observation @Observable final class PointOfSaleSettingsService { @@ -16,7 +20,7 @@ import Observation var storeName: String { guard let site = ServiceLocator.stores.sessionManager.defaultSite else { - return "Not set" + return Localization.storeNotSet } return site.name } @@ -79,3 +83,13 @@ import Observation return value?.isEmpty == true ? nil : value } } + +private extension PointOfSaleSettingsService { + enum Localization { + static let storeNotSet = NSLocalizedString( + "pointOfSaleSettingsService.storeNotSet", + value: "Not set", + comment: "Text displayed on Point of Sale settings when store has not been provided." + ) + } +} diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index ebc45760788..269a8f11d7c 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -1,5 +1,4 @@ import SwiftUI -import Yosemite struct PointOfSaleSettingsStoreDetailView: View { let posSettingsService: PointOfSaleSettingsService From 8a02151284bbaa37a22b0d65db01966fc2087184 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Mon, 25 Aug 2025 17:05:27 +0700 Subject: [PATCH 11/29] make setting view create its own service --- .../Models/PointOfSaleSettingsService.swift | 25 +++++-------------- .../PointOfSaleSettingsStoreDetailView.swift | 2 +- .../Settings/PointOfSaleSettingsView.swift | 18 ++++++++++--- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift index 461e219c5a2..ceef69b2880 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift @@ -6,6 +6,9 @@ import class Yosemite.PluginsService import Observation @Observable final class PointOfSaleSettingsService { + private let siteID: Int64 + private(set) var storeName: String + private(set) var receiptStoreName: String? private(set) var receiptStoreAddress: String? private(set) var receiptStorePhone: String? @@ -14,15 +17,9 @@ import Observation private(set) var isLoading: Bool = false private(set) var shouldShowReceiptInformation: Bool = false - private var siteID: Int64 { - ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 - } - - var storeName: String { - guard let site = ServiceLocator.stores.sessionManager.defaultSite else { - return Localization.storeNotSet - } - return site.name + init(siteID: Int64, storeName: String) { + self.siteID = siteID + self.storeName = storeName } var storeAddress: String { @@ -83,13 +80,3 @@ import Observation return value?.isEmpty == true ? nil : value } } - -private extension PointOfSaleSettingsService { - enum Localization { - static let storeNotSet = NSLocalizedString( - "pointOfSaleSettingsService.storeNotSet", - value: "Not set", - comment: "Text displayed on Point of Sale settings when store has not been provided." - ) - } -} diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index 269a8f11d7c..e55d0b9ae20 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -125,5 +125,5 @@ private extension PointOfSaleSettingsStoreDetailView { } #Preview { - PointOfSaleSettingsStoreDetailView(posSettingsService: PointOfSaleSettingsService()) + PointOfSaleSettingsStoreDetailView(posSettingsService: PointOfSaleSettingsService(siteID: 123, storeName: "POS Store")) } diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift index c0f6f36f9e3..342feb2f1a8 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift @@ -3,7 +3,13 @@ import SwiftUI struct PointOfSaleSettingsView: View { @Environment(\.dismiss) private var dismiss @State private var selection: SidebarNavigation? = .store - @State private var settingsService = PointOfSaleSettingsService() + + private var settingsService: PointOfSaleSettingsService? { + guard let site = ServiceLocator.stores.sessionManager.defaultSite else { + return nil + } + return PointOfSaleSettingsService(siteID: site.siteID, storeName: site.name) + } var body: some View { POSPageHeaderView( @@ -67,7 +73,11 @@ struct PointOfSaleSettingsView: View { Group { switch selection { case .store: - PointOfSaleSettingsStoreDetailView(posSettingsService: settingsService) + if let settingsService { + PointOfSaleSettingsStoreDetailView(posSettingsService: settingsService) + } else { + EmptyView() + } case .hardware: PointOfSaleSettingsHardwareDetailView() case .help: @@ -79,7 +89,9 @@ struct PointOfSaleSettingsView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) } .task { - await settingsService.retrievePOSReceiptSettings() + if let settingsService { + await settingsService.retrievePOSReceiptSettings() + } } } } From fe32ce48add950df5488e756246b06bd3129bcc8 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Mon, 25 Aug 2025 17:13:01 +0700 Subject: [PATCH 12/29] Revert "make setting view create its own service" This reverts commit 8a02151284bbaa37a22b0d65db01966fc2087184. --- .../Models/PointOfSaleSettingsService.swift | 25 ++++++++++++++----- .../PointOfSaleSettingsStoreDetailView.swift | 2 +- .../Settings/PointOfSaleSettingsView.swift | 18 +++---------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift index ceef69b2880..461e219c5a2 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift @@ -6,9 +6,6 @@ import class Yosemite.PluginsService import Observation @Observable final class PointOfSaleSettingsService { - private let siteID: Int64 - private(set) var storeName: String - private(set) var receiptStoreName: String? private(set) var receiptStoreAddress: String? private(set) var receiptStorePhone: String? @@ -17,9 +14,15 @@ import Observation private(set) var isLoading: Bool = false private(set) var shouldShowReceiptInformation: Bool = false - init(siteID: Int64, storeName: String) { - self.siteID = siteID - self.storeName = storeName + private var siteID: Int64 { + ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 + } + + var storeName: String { + guard let site = ServiceLocator.stores.sessionManager.defaultSite else { + return Localization.storeNotSet + } + return site.name } var storeAddress: String { @@ -80,3 +83,13 @@ import Observation return value?.isEmpty == true ? nil : value } } + +private extension PointOfSaleSettingsService { + enum Localization { + static let storeNotSet = NSLocalizedString( + "pointOfSaleSettingsService.storeNotSet", + value: "Not set", + comment: "Text displayed on Point of Sale settings when store has not been provided." + ) + } +} diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index e55d0b9ae20..269a8f11d7c 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -125,5 +125,5 @@ private extension PointOfSaleSettingsStoreDetailView { } #Preview { - PointOfSaleSettingsStoreDetailView(posSettingsService: PointOfSaleSettingsService(siteID: 123, storeName: "POS Store")) + PointOfSaleSettingsStoreDetailView(posSettingsService: PointOfSaleSettingsService()) } diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift index 342feb2f1a8..c0f6f36f9e3 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift @@ -3,13 +3,7 @@ import SwiftUI struct PointOfSaleSettingsView: View { @Environment(\.dismiss) private var dismiss @State private var selection: SidebarNavigation? = .store - - private var settingsService: PointOfSaleSettingsService? { - guard let site = ServiceLocator.stores.sessionManager.defaultSite else { - return nil - } - return PointOfSaleSettingsService(siteID: site.siteID, storeName: site.name) - } + @State private var settingsService = PointOfSaleSettingsService() var body: some View { POSPageHeaderView( @@ -73,11 +67,7 @@ struct PointOfSaleSettingsView: View { Group { switch selection { case .store: - if let settingsService { - PointOfSaleSettingsStoreDetailView(posSettingsService: settingsService) - } else { - EmptyView() - } + PointOfSaleSettingsStoreDetailView(posSettingsService: settingsService) case .hardware: PointOfSaleSettingsHardwareDetailView() case .help: @@ -89,9 +79,7 @@ struct PointOfSaleSettingsView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) } .task { - if let settingsService { - await settingsService.retrievePOSReceiptSettings() - } + await settingsService.retrievePOSReceiptSettings() } } } From e19018b09b9b80df7ebe95d08a4f4b62b10f8bdf Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 26 Aug 2025 13:17:03 +0700 Subject: [PATCH 13/29] split settings service into controller-service --- .../POS/PointOfSaleSettingsService.swift | 33 ++++++++++++++++++ ...ft => PointOfSaleSettingsController.swift} | 34 +++++++++---------- .../PointOfSaleDashboardView.swift | 11 +++++- .../PointOfSaleSettingsStoreDetailView.swift | 26 +++++++------- .../Settings/PointOfSaleSettingsView.swift | 13 +++---- .../WooCommerce.xcodeproj/project.pbxproj | 8 ++--- 6 files changed, 84 insertions(+), 41 deletions(-) create mode 100644 Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift rename WooCommerce/Classes/POS/Models/{PointOfSaleSettingsService.swift => PointOfSaleSettingsController.swift} (77%) diff --git a/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift b/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift new file mode 100644 index 00000000000..d0c2e5c8b66 --- /dev/null +++ b/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift @@ -0,0 +1,33 @@ + +import Foundation +import Networking +import Storage + +public final class PointOfSaleSettingsService { + // TODO: make private once we've moved the actions here. + public private(set) var siteID: Int64 + private let settingStoreMethods: SettingStoreMethodsProtocol + private let storage: StorageManagerType + + init(siteID: Int64, + settingStoreMethods: SettingStoreMethodsProtocol, + storage: StorageManagerType) { + self.siteID = siteID + self.settingStoreMethods = settingStoreMethods + self.storage = storage + } + + public convenience init(siteID: Int64, + credentials: Credentials?, + storage: StorageManagerType) { + let network = AlamofireNetwork(credentials: credentials) + self.init(siteID: siteID, + settingStoreMethods: SettingStoreMethods(storageManager: storage, + network: network), + storage: storage) + } + + public func retrievePointOfSaleSettings() async throws -> [SiteSetting] { + return try await settingStoreMethods.retrievePointOfSaleSettings(siteID: siteID) + } +} diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift similarity index 77% rename from WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift rename to WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift index 461e219c5a2..153c2e68d54 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsService.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift @@ -1,11 +1,12 @@ import Foundation import struct Yosemite.SiteSetting -import enum Yosemite.SettingAction import enum Yosemite.Plugin import class Yosemite.PluginsService import Observation -@Observable final class PointOfSaleSettingsService { +import class Yosemite.PointOfSaleSettingsService + +@Observable final class PointOfSaleSettingsController { private(set) var receiptStoreName: String? private(set) var receiptStoreAddress: String? private(set) var receiptStorePhone: String? @@ -14,8 +15,14 @@ import Observation private(set) var isLoading: Bool = false private(set) var shouldShowReceiptInformation: Bool = false + private let settingsService: PointOfSaleSettingsService + + init(settingsService: PointOfSaleSettingsService) { + self.settingsService = settingsService + } + private var siteID: Int64 { - ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 + settingsService.siteID } var storeName: String { @@ -40,20 +47,13 @@ import Observation return } - await withCheckedContinuation { (continuation: CheckedContinuation) in - let action = SettingAction.retrievePointOfSaleSettings(siteID: siteID) { [weak self] result in - guard let self else { return } - switch result { - case .success(let siteSettings): - updateReceiptSettings(from: siteSettings) - case .failure(let error): - DDLogError("Failed to load POS settings: \(error)") - } - isLoading = false - continuation.resume() - } - ServiceLocator.stores.dispatch(action) + do { + let siteSettings = try await settingsService.retrievePointOfSaleSettings() + updateReceiptSettings(from: siteSettings) + } catch { + DDLogError("Failed to load POS settings: \(error)") } + isLoading = false } @MainActor @@ -84,7 +84,7 @@ import Observation } } -private extension PointOfSaleSettingsService { +private extension PointOfSaleSettingsController { enum Localization { static let storeNotSet = NSLocalizedString( "pointOfSaleSettingsService.storeNotSet", diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift index 46f2a817bb8..35a5cdd2a69 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift @@ -1,4 +1,5 @@ import SwiftUI +import class Yosemite.PointOfSaleSettingsService @available(iOS 17.0, *) struct PointOfSaleDashboardView: View { @@ -133,7 +134,15 @@ struct PointOfSaleDashboardView: View { documentationView } .posFullScreenCover(isPresented: $showSettings) { - PointOfSaleSettingsView() + let siteID = ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 + let credentials = ServiceLocator.stores.sessionManager.defaultCredentials + let storage = ServiceLocator.storageManager + let service = PointOfSaleSettingsService(siteID: siteID, + credentials: credentials, + storage: storage) + let controller = PointOfSaleSettingsController(settingsService: service) + + PointOfSaleSettingsView(settingsController: controller) } .onChange(of: posModel.entryPointController.eligibilityState) { oldValue, newValue in guard newValue == .eligible else { return } diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index 269a8f11d7c..652a01c38d0 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -1,7 +1,7 @@ import SwiftUI struct PointOfSaleSettingsStoreDetailView: View { - let posSettingsService: PointOfSaleSettingsService + let settingsController: PointOfSaleSettingsController var body: some View { NavigationStack { @@ -11,12 +11,12 @@ struct PointOfSaleSettingsStoreDetailView: View { .font(.posBodyLargeRegular()) Text(Localization.storeName) - Text(posSettingsService.storeName) + Text(settingsController.storeName) .font(.posBodyMediumRegular()) .foregroundStyle(.secondary) Text(Localization.address) - Text(posSettingsService.storeAddress) + Text(settingsController.storeAddress) .font(.posBodyMediumRegular()) .foregroundStyle(.secondary) } @@ -26,22 +26,22 @@ struct PointOfSaleSettingsStoreDetailView: View { Text(Localization.receiptInformation) .font(.posBodyLargeRegular()) Text(Localization.receiptStoreName) - settingValueView(for: posSettingsService.receiptStoreName) + settingValueView(for: settingsController.receiptStoreName) Text(Localization.physicalAddress) - settingValueView(for: posSettingsService.receiptStoreAddress) + settingValueView(for: settingsController.receiptStoreAddress) Text(Localization.phoneNumber) - settingValueView(for: posSettingsService.receiptStorePhone) + settingValueView(for: settingsController.receiptStorePhone) Text(Localization.email) - settingValueView(for: posSettingsService.receiptStoreEmail) + settingValueView(for: settingsController.receiptStoreEmail) Text(Localization.refundReturnsPolicy) - settingValueView(for: posSettingsService.receiptRefundReturnsPolicy) + settingValueView(for: settingsController.receiptRefundReturnsPolicy) } - .renderedIf(posSettingsService.shouldShowReceiptInformation) + .renderedIf(settingsController.shouldShowReceiptInformation) } .padding() } @@ -49,7 +49,7 @@ struct PointOfSaleSettingsStoreDetailView: View { @ViewBuilder private func settingValueView(for value: String?) -> some View { - if posSettingsService.isLoading { + if settingsController.isLoading { ProgressView() .font(.posBodyLargeRegular()) } else { @@ -124,6 +124,6 @@ private extension PointOfSaleSettingsStoreDetailView { } } -#Preview { - PointOfSaleSettingsStoreDetailView(posSettingsService: PointOfSaleSettingsService()) -} +//#Preview { +// PointOfSaleSettingsStoreDetailView(posSettingsService: PointOfSaleSettingsController()) +//} diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift index c0f6f36f9e3..f4a5e9afb65 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift @@ -3,7 +3,8 @@ import SwiftUI struct PointOfSaleSettingsView: View { @Environment(\.dismiss) private var dismiss @State private var selection: SidebarNavigation? = .store - @State private var settingsService = PointOfSaleSettingsService() + + let settingsController: PointOfSaleSettingsController var body: some View { POSPageHeaderView( @@ -67,7 +68,7 @@ struct PointOfSaleSettingsView: View { Group { switch selection { case .store: - PointOfSaleSettingsStoreDetailView(posSettingsService: settingsService) + PointOfSaleSettingsStoreDetailView(settingsController: settingsController) case .hardware: PointOfSaleSettingsHardwareDetailView() case .help: @@ -79,7 +80,7 @@ struct PointOfSaleSettingsView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) } .task { - await settingsService.retrievePOSReceiptSettings() + await settingsController.retrievePOSReceiptSettings() } } } @@ -222,6 +223,6 @@ private extension PointOfSaleSettingsView { } } -#Preview { - PointOfSaleSettingsView() -} +//#Preview { +// PointOfSaleSettingsView() +//} diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 118fac29982..ee8a4b8f160 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -1648,7 +1648,7 @@ 68E952CC287536010095A23D /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E952CB287536010095A23D /* SafariView.swift */; }; 68E952D0287587BF0095A23D /* CardReaderManualRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E952CF287587BF0095A23D /* CardReaderManualRowView.swift */; }; 68E952D22875A44B0095A23D /* CardReaderType+Manual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E952D12875A44B0095A23D /* CardReaderType+Manual.swift */; }; - 68E9F7012E5C499200D45747 /* PointOfSaleSettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E9F7002E5C499000D45747 /* PointOfSaleSettingsService.swift */; }; + 68E9F7012E5C499200D45747 /* PointOfSaleSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68E9F7002E5C499000D45747 /* PointOfSaleSettingsController.swift */; }; 68ED2BD62ADD2C8C00ECA88D /* LineDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68ED2BD52ADD2C8C00ECA88D /* LineDetailView.swift */; }; 68F151E12C0DA7910082AEC8 /* Cart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68F151E02C0DA7910082AEC8 /* Cart.swift */; }; 68F68A502D6730E200BB9568 /* POSCollectOrderPaymentAnalyticsTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68F68A4F2D6730DF00BB9568 /* POSCollectOrderPaymentAnalyticsTracking.swift */; }; @@ -4825,7 +4825,7 @@ 68E952CB287536010095A23D /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = ""; }; 68E952CF287587BF0095A23D /* CardReaderManualRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderManualRowView.swift; sourceTree = ""; }; 68E952D12875A44B0095A23D /* CardReaderType+Manual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardReaderType+Manual.swift"; sourceTree = ""; }; - 68E9F7002E5C499000D45747 /* PointOfSaleSettingsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleSettingsService.swift; sourceTree = ""; }; + 68E9F7002E5C499000D45747 /* PointOfSaleSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleSettingsController.swift; sourceTree = ""; }; 68ED2BD52ADD2C8C00ECA88D /* LineDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineDetailView.swift; sourceTree = ""; }; 68F151E02C0DA7910082AEC8 /* Cart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cart.swift; sourceTree = ""; }; 68F68A4F2D6730DF00BB9568 /* POSCollectOrderPaymentAnalyticsTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSCollectOrderPaymentAnalyticsTracking.swift; sourceTree = ""; }; @@ -9968,7 +9968,7 @@ children = ( 01B3A1F12DB6D48800286B7F /* ItemListType.swift */, 20FCBCDC2CE223340082DCA3 /* PointOfSaleAggregateModel.swift */, - 68E9F7002E5C499000D45747 /* PointOfSaleSettingsService.swift */, + 68E9F7002E5C499000D45747 /* PointOfSaleSettingsController.swift */, 209ECA802DB8FC280089F3D2 /* PointOfSaleViewStateCoordinator.swift */, 20C6E7502CDE4AEA00CD124C /* ItemListState.swift */, 20F7B12C2D12C7B900C08193 /* ItemsContainerState.swift */, @@ -15579,7 +15579,7 @@ CE21B3D720FE669A00A259D5 /* BasicTableViewCell.swift in Sources */, 451A04EA2386D28300E368C9 /* ProductImagesHeaderViewModel.swift in Sources */, 02307924258731B2008EADEE /* PrintShippingLabelViewModel.swift in Sources */, - 68E9F7012E5C499200D45747 /* PointOfSaleSettingsService.swift in Sources */, + 68E9F7012E5C499200D45747 /* PointOfSaleSettingsController.swift in Sources */, D843D5D92248EE91001BFA55 /* ManualTrackingViewModel.swift in Sources */, 20886D3D2D96E0F900F7AE03 /* PointOfSaleCardPresentPaymentConnectingFailedLocationRequiredAlertViewModel.swift in Sources */, 204CB8102C10BB88000C9773 /* CardPresentPaymentPreviewService.swift in Sources */, From c3c2ca06b3e273e741d3f7fea74c5be755541afc Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 26 Aug 2025 13:24:13 +0700 Subject: [PATCH 14/29] extract controller init to posmodel --- .../POS/Models/PointOfSaleAggregateModel.swift | 13 +++++++++++++ .../POS/Presentation/PointOfSaleDashboardView.swift | 10 +--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index e40ffa2a39f..d354c3f9d2e 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -14,6 +14,8 @@ import enum Yosemite.POSItemType import protocol Yosemite.PointOfSaleBarcodeScanServiceProtocol import enum Yosemite.PointOfSaleBarcodeScanError +import class Yosemite.PointOfSaleSettingsService + @available(iOS 17.0, *) protocol PointOfSaleAggregateModelProtocol { var orderStage: PointOfSaleOrderStage { get } @@ -72,6 +74,7 @@ protocol PointOfSaleAggregateModelProtocol { let popularPurchasableItemsController: PointOfSaleItemsControllerProtocol let couponsController: PointOfSaleCouponsControllerProtocol let couponsSearchController: PointOfSaleSearchingItemsControllerProtocol + let settingsController: PointOfSaleSettingsController private let cardPresentPaymentService: CardPresentPaymentFacade private let orderController: PointOfSaleOrderControllerProtocol @@ -129,6 +132,16 @@ protocol PointOfSaleAggregateModelProtocol { self.barcodeScanService = barcodeScanService self.soundPlayer = soundPlayer + // + let siteID = ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 + let credentials = ServiceLocator.stores.sessionManager.defaultCredentials + let storage = ServiceLocator.storageManager + let service = PointOfSaleSettingsService(siteID: siteID, + credentials: credentials, + storage: storage) + + self.settingsController = PointOfSaleSettingsController(settingsService: service) + publishCardReaderConnectionStatus() publishPaymentMessages() setupReaderReconnectionObservation() diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift index 35a5cdd2a69..b5ef20bc936 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift @@ -134,15 +134,7 @@ struct PointOfSaleDashboardView: View { documentationView } .posFullScreenCover(isPresented: $showSettings) { - let siteID = ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 - let credentials = ServiceLocator.stores.sessionManager.defaultCredentials - let storage = ServiceLocator.storageManager - let service = PointOfSaleSettingsService(siteID: siteID, - credentials: credentials, - storage: storage) - let controller = PointOfSaleSettingsController(settingsService: service) - - PointOfSaleSettingsView(settingsController: controller) + PointOfSaleSettingsView(settingsController: posModel.settingsController) } .onChange(of: posModel.entryPointController.eligibilityState) { oldValue, newValue in guard newValue == .eligible else { return } From 8f91b683c9742350613ec175df8320c4711e6b45 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 26 Aug 2025 13:44:06 +0700 Subject: [PATCH 15/29] make protocol. DI settings controller into POS aggregate --- .../Models/PointOfSaleAggregateModel.swift | 20 ++++++------ .../PointOfSaleSettingsController.swift | 32 ++++++++++++++++++- .../PointOfSaleEntryPointView.swift | 5 +++ .../PointOfSaleSettingsStoreDetailView.swift | 2 +- .../Settings/PointOfSaleSettingsView.swift | 4 +-- .../POS/TabBar/POSTabCoordinator.swift | 4 +++ .../Classes/POS/Utils/PreviewHelpers.swift | 2 ++ 7 files changed, 56 insertions(+), 13 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index d354c3f9d2e..aad723645f0 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -74,7 +74,7 @@ protocol PointOfSaleAggregateModelProtocol { let popularPurchasableItemsController: PointOfSaleItemsControllerProtocol let couponsController: PointOfSaleCouponsControllerProtocol let couponsSearchController: PointOfSaleSearchingItemsControllerProtocol - let settingsController: PointOfSaleSettingsController + let settingsController: PointOfSaleSettingsControllerProtocol private let cardPresentPaymentService: CardPresentPaymentFacade private let orderController: PointOfSaleOrderControllerProtocol @@ -110,6 +110,7 @@ protocol PointOfSaleAggregateModelProtocol { couponsSearchController: PointOfSaleSearchingItemsControllerProtocol, cardPresentPaymentService: CardPresentPaymentFacade, orderController: PointOfSaleOrderControllerProtocol, + settingsController: PointOfSaleSettingsControllerProtocol, analytics: Analytics = ServiceLocator.analytics, collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking, searchHistoryService: POSSearchHistoryProviding, @@ -124,6 +125,7 @@ protocol PointOfSaleAggregateModelProtocol { self.couponsSearchController = couponsSearchController self.cardPresentPaymentService = cardPresentPaymentService self.orderController = orderController + self.settingsController = settingsController self.analytics = analytics self.collectOrderPaymentAnalyticsTracker = collectOrderPaymentAnalyticsTracker self.searchHistoryService = searchHistoryService @@ -133,14 +135,14 @@ protocol PointOfSaleAggregateModelProtocol { self.soundPlayer = soundPlayer // - let siteID = ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 - let credentials = ServiceLocator.stores.sessionManager.defaultCredentials - let storage = ServiceLocator.storageManager - let service = PointOfSaleSettingsService(siteID: siteID, - credentials: credentials, - storage: storage) - - self.settingsController = PointOfSaleSettingsController(settingsService: service) +// let siteID = ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 +// let credentials = ServiceLocator.stores.sessionManager.defaultCredentials +// let storage = ServiceLocator.storageManager +// let service = PointOfSaleSettingsService(siteID: siteID, +// credentials: credentials, +// storage: storage) +// +// self.settingsController = PointOfSaleSettingsController(settingsService: service) publishCardReaderConnectionStatus() publishPaymentMessages() diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift index 153c2e68d54..6c1f1502b5f 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift @@ -6,7 +6,37 @@ import Observation import class Yosemite.PointOfSaleSettingsService -@Observable final class PointOfSaleSettingsController { +protocol PointOfSaleSettingsControllerProtocol { + var receiptStoreName: String? { get } + var receiptStoreAddress: String? { get } + var receiptStorePhone: String? { get } + var receiptStoreEmail: String? { get } + var receiptRefundReturnsPolicy: String? { get } + var isLoading: Bool { get } + var shouldShowReceiptInformation: Bool { get } + var storeName: String { get } + var storeAddress: String { get } + + func retrievePOSReceiptSettings() async +} + +class PointOfSaleSettingsPreviewController: PointOfSaleSettingsControllerProtocol { + var receiptStoreName: String? = "Sample Store" + var receiptStoreAddress: String? = "123 Main Street\nAnytown, ST 12345" + var receiptStorePhone: String? = "+1 (555) 123-4567" + var receiptStoreEmail: String? = "store@example.com" + var receiptRefundReturnsPolicy: String? = "30-day return policy" + var isLoading: Bool = false + var shouldShowReceiptInformation: Bool = true + var storeName: String = "Sample Store" + var storeAddress: String = "123 Main Street, Anytown, ST 12345" + + func retrievePOSReceiptSettings() async { + // no-op + } +} + +@Observable final class PointOfSaleSettingsController: PointOfSaleSettingsControllerProtocol { private(set) var receiptStoreName: String? private(set) var receiptStoreAddress: String? private(set) var receiptStorePhone: String? diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift index c08fedf2ac7..9f6339f41c0 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleEntryPointView.swift @@ -18,6 +18,7 @@ struct PointOfSaleEntryPointView: View { private let couponsSearchController: PointOfSaleSearchingItemsControllerProtocol private let cardPresentPaymentService: CardPresentPaymentFacade private let orderController: PointOfSaleOrderControllerProtocol + private let settingsController: PointOfSaleSettingsControllerProtocol private let collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking private let searchHistoryService: POSSearchHistoryProviding private let popularPurchasableItemsController: PointOfSaleItemsControllerProtocol @@ -30,6 +31,7 @@ struct PointOfSaleEntryPointView: View { onPointOfSaleModeActiveStateChange: @escaping ((Bool) -> Void), cardPresentPaymentService: CardPresentPaymentFacade, orderController: PointOfSaleOrderControllerProtocol, + settingsController: PointOfSaleSettingsControllerProtocol, collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking, searchHistoryService: POSSearchHistoryProviding, popularPurchasableItemsController: PointOfSaleItemsControllerProtocol, @@ -43,6 +45,7 @@ struct PointOfSaleEntryPointView: View { self.couponsSearchController = couponsSearchController self.cardPresentPaymentService = cardPresentPaymentService self.orderController = orderController + self.settingsController = settingsController self.collectOrderPaymentAnalyticsTracker = collectOrderPaymentAnalyticsTracker self.searchHistoryService = searchHistoryService self.popularPurchasableItemsController = popularPurchasableItemsController @@ -71,6 +74,7 @@ struct PointOfSaleEntryPointView: View { couponsSearchController: couponsSearchController, cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, + settingsController: settingsController, collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker, searchHistoryService: searchHistoryService, popularPurchasableItemsController: popularPurchasableItemsController, @@ -101,6 +105,7 @@ struct PointOfSaleEntryPointView: View { onPointOfSaleModeActiveStateChange: { _ in }, cardPresentPaymentService: CardPresentPaymentPreviewService(), orderController: PointOfSalePreviewOrderController(), + settingsController: PointOfSaleSettingsPreviewController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentPreviewAnalytics(), searchHistoryService: PointOfSalePreviewHistoryService(), popularPurchasableItemsController: PointOfSalePreviewItemsController(), diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index 652a01c38d0..8bed1b7a8b0 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -1,7 +1,7 @@ import SwiftUI struct PointOfSaleSettingsStoreDetailView: View { - let settingsController: PointOfSaleSettingsController + let settingsController: PointOfSaleSettingsControllerProtocol var body: some View { NavigationStack { diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift index f4a5e9afb65..5276b478cd5 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift @@ -3,8 +3,8 @@ import SwiftUI struct PointOfSaleSettingsView: View { @Environment(\.dismiss) private var dismiss @State private var selection: SidebarNavigation? = .store - - let settingsController: PointOfSaleSettingsController + + let settingsController: PointOfSaleSettingsControllerProtocol var body: some View { POSPageHeaderView( diff --git a/WooCommerce/Classes/POS/TabBar/POSTabCoordinator.swift b/WooCommerce/Classes/POS/TabBar/POSTabCoordinator.swift index 23492bdb61b..96548fcd034 100644 --- a/WooCommerce/Classes/POS/TabBar/POSTabCoordinator.swift +++ b/WooCommerce/Classes/POS/TabBar/POSTabCoordinator.swift @@ -92,6 +92,9 @@ private extension POSTabCoordinator { let cardPresentPaymentService = await CardPresentPaymentService(siteID: siteID, stores: storesManager, collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker) + let settingsService = PointOfSaleSettingsService(siteID: siteID, + credentials: credentials, + storage: storageManager) if let receiptService = POSReceiptService(siteID: siteID, credentials: credentials), let orderService = POSOrderService(siteID: siteID, @@ -118,6 +121,7 @@ private extension POSTabCoordinator { cardPresentPaymentService: cardPresentPaymentService, orderController: PointOfSaleOrderController(orderService: orderService, receiptService: receiptService), + settingsController: PointOfSaleSettingsController(settingsService: settingsService), collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker, searchHistoryService: POSSearchHistoryService(siteID: siteID), popularPurchasableItemsController: PointOfSaleItemsController( diff --git a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift index 89706617730..c0e6d0dbfb9 100644 --- a/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift +++ b/WooCommerce/Classes/POS/Utils/PreviewHelpers.swift @@ -213,6 +213,7 @@ struct POSPreviewHelpers { couponsSearchController: PointOfSaleCouponsControllerProtocol = PointOfSalePreviewCouponsController(), cardPresentPaymentService: CardPresentPaymentFacade = CardPresentPaymentPreviewService(), orderController: PointOfSaleOrderControllerProtocol = PointOfSalePreviewOrderController(), + settingsController: PointOfSaleSettingsControllerProtocol = PointOfSaleSettingsPreviewController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking = POSCollectOrderPaymentPreviewAnalytics(), searchHistoryService: POSSearchHistoryProviding = PointOfSalePreviewHistoryService(), popularItemsController: PointOfSaleItemsControllerProtocol = PointOfSalePreviewItemsController(), @@ -226,6 +227,7 @@ struct POSPreviewHelpers { couponsSearchController: couponsSearchController, cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, + settingsController: settingsController, collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker, searchHistoryService: searchHistoryService, popularPurchasableItemsController: popularItemsController, From a626d12089a1f2fc03b1c896c62a7fa949343e56 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 26 Aug 2025 15:30:56 +0700 Subject: [PATCH 16/29] make test target compile --- .../POS/Models/PointOfSaleAggregateModelTests.swift | 2 ++ .../POS/Presentation/POSItemActionHandlerTests.swift | 2 ++ 2 files changed, 4 insertions(+) diff --git a/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift b/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift index fc3dffbcda1..c24cb1d8436 100644 --- a/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift @@ -1017,6 +1017,7 @@ private func makePointOfSaleAggregateModel( couponsSearchController: PointOfSaleSearchingItemsControllerProtocol = MockPointOfSaleCouponsController(), cardPresentPaymentService: CardPresentPaymentFacade = MockCardPresentPaymentService(), orderController: PointOfSaleOrderControllerProtocol = MockPointOfSaleOrderController(), + settingsController: PointOfSaleSettingsControllerProtocol = PointOfSaleSettingsPreviewController(), analytics: Analytics = WooAnalytics(analyticsProvider: MockAnalyticsProvider()), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking = MockPOSCollectOrderPaymentAnalyticsTracker(), searchHistoryService: POSSearchHistoryProviding = MockPOSSearchHistoryService(), @@ -1033,6 +1034,7 @@ private func makePointOfSaleAggregateModel( couponsSearchController: couponsSearchController, cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, + settingsController: settingsController, analytics: analytics, collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker, searchHistoryService: searchHistoryService, diff --git a/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerTests.swift b/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerTests.swift index 2fece6e897b..ddd9bce4843 100644 --- a/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerTests.swift @@ -106,6 +106,7 @@ private func makePointOfSaleAggregateModel( couponsSearchController: PointOfSaleSearchingItemsControllerProtocol = MockPointOfSaleCouponsController(), cardPresentPaymentService: CardPresentPaymentFacade = MockCardPresentPaymentService(), orderController: PointOfSaleOrderControllerProtocol = MockPointOfSaleOrderController(), + settingsController: PointOfSaleSettingsControllerProtocol = PointOfSaleSettingsPreviewController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking = MockPOSCollectOrderPaymentAnalyticsTracker(), searchHistoryService: POSSearchHistoryProviding = MockPOSSearchHistoryService(), popularPurchasableItemsController: PointOfSaleItemsControllerProtocol = MockPointOfSaleItemsController(), @@ -119,6 +120,7 @@ private func makePointOfSaleAggregateModel( couponsSearchController: couponsSearchController, cardPresentPaymentService: cardPresentPaymentService, orderController: orderController, + settingsController: settingsController, collectOrderPaymentAnalyticsTracker: collectOrderPaymentAnalyticsTracker, searchHistoryService: searchHistoryService, popularPurchasableItemsController: popularPurchasableItemsController, From b102193dab0307673a244d15db9a6009ee2a44d9 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 26 Aug 2025 15:50:19 +0700 Subject: [PATCH 17/29] DI storage into settings service --- .../POS/PointOfSaleSettingsService.swift | 1 - .../Models/PointOfSaleAggregateModel.swift | 10 --------- .../PointOfSaleSettingsController.swift | 21 ++++++++++--------- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift b/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift index d0c2e5c8b66..17333e1f72d 100644 --- a/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift +++ b/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift @@ -4,7 +4,6 @@ import Networking import Storage public final class PointOfSaleSettingsService { - // TODO: make private once we've moved the actions here. public private(set) var siteID: Int64 private let settingStoreMethods: SettingStoreMethodsProtocol private let storage: StorageManagerType diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index aad723645f0..f1e3a420f2f 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -134,16 +134,6 @@ protocol PointOfSaleAggregateModelProtocol { self.barcodeScanService = barcodeScanService self.soundPlayer = soundPlayer - // -// let siteID = ServiceLocator.stores.sessionManager.defaultSite?.siteID ?? 0 -// let credentials = ServiceLocator.stores.sessionManager.defaultCredentials -// let storage = ServiceLocator.storageManager -// let service = PointOfSaleSettingsService(siteID: siteID, -// credentials: credentials, -// storage: storage) -// -// self.settingsController = PointOfSaleSettingsController(settingsService: service) - publishCardReaderConnectionStatus() publishPaymentMessages() setupReaderReconnectionObservation() diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift index 6c1f1502b5f..67398a9b6eb 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift @@ -5,6 +5,7 @@ import class Yosemite.PluginsService import Observation import class Yosemite.PointOfSaleSettingsService +import Storage protocol PointOfSaleSettingsControllerProtocol { var receiptStoreName: String? { get } @@ -16,7 +17,7 @@ protocol PointOfSaleSettingsControllerProtocol { var shouldShowReceiptInformation: Bool { get } var storeName: String { get } var storeAddress: String { get } - + func retrievePOSReceiptSettings() async } @@ -51,10 +52,6 @@ class PointOfSaleSettingsPreviewController: PointOfSaleSettingsControllerProtoco self.settingsService = settingsService } - private var siteID: Int64 { - settingsService.siteID - } - var storeName: String { guard let site = ServiceLocator.stores.sessionManager.defaultSite else { return Localization.storeNotSet @@ -70,7 +67,7 @@ class PointOfSaleSettingsPreviewController: PointOfSaleSettingsControllerProtoco func retrievePOSReceiptSettings() async { isLoading = true - shouldShowReceiptInformation = await isPluginSupported(.wooCommerce, minimumVersion: "10.0") + shouldShowReceiptInformation = await isPluginSupported(.wooCommerce, minimumVersion: Constants.minimumWooCommerceVersion) guard shouldShowReceiptInformation else { isLoading = false @@ -87,11 +84,11 @@ class PointOfSaleSettingsPreviewController: PointOfSaleSettingsControllerProtoco } @MainActor - private func isPluginSupported(_ plugin: Plugin, minimumVersion: String) async -> Bool { - let storageManager = ServiceLocator.storageManager + private func isPluginSupported(_ plugin: Plugin, + storageManager: StorageManagerType = ServiceLocator.storageManager, + minimumVersion: String) async -> Bool { let pluginsService = PluginsService(storageManager: storageManager) - guard let systemPlugin = pluginsService.loadPluginInStorage(siteID: siteID, plugin: plugin, isActive: true), - systemPlugin.active else { + guard let systemPlugin = pluginsService.loadPluginInStorage(siteID: settingsService.siteID, plugin: plugin, isActive: true), systemPlugin.active else { return false } @@ -115,6 +112,10 @@ class PointOfSaleSettingsPreviewController: PointOfSaleSettingsControllerProtoco } private extension PointOfSaleSettingsController { + enum Constants { + static let minimumWooCommerceVersion: String = "10.0" + } + enum Localization { static let storeNotSet = NSLocalizedString( "pointOfSaleSettingsService.storeNotSet", From f3de3befbfe1a418f8f1cda0cc5a75b3d5d8afd4 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 26 Aug 2025 16:14:20 +0700 Subject: [PATCH 18/29] DI default values for store name and address --- .../PointOfSaleSettingsController.swift | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift index 67398a9b6eb..3ceaa79654e 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift @@ -30,7 +30,10 @@ class PointOfSaleSettingsPreviewController: PointOfSaleSettingsControllerProtoco var isLoading: Bool = false var shouldShowReceiptInformation: Bool = true var storeName: String = "Sample Store" - var storeAddress: String = "123 Main Street, Anytown, ST 12345" + + var storeAddress: String { + "123 Main Street\nAnytown, ST 12345" + } func retrievePOSReceiptSettings() async { // no-op @@ -46,21 +49,28 @@ class PointOfSaleSettingsPreviewController: PointOfSaleSettingsControllerProtoco private(set) var isLoading: Bool = false private(set) var shouldShowReceiptInformation: Bool = false + private let defaultSiteName: String? private let settingsService: PointOfSaleSettingsService + private let siteSettings: [SiteSetting] - init(settingsService: PointOfSaleSettingsService) { + init(settingsService: PointOfSaleSettingsService, + defaultSiteName: String? = ServiceLocator.stores.sessionManager.defaultSite?.name, + siteSettings: [SiteSetting] = ServiceLocator.selectedSiteSettings.siteSettings) { self.settingsService = settingsService + self.defaultSiteName = defaultSiteName + self.siteSettings = siteSettings } var storeName: String { - guard let site = ServiceLocator.stores.sessionManager.defaultSite else { + if let defaultSiteName { + return defaultSiteName + } else { return Localization.storeNotSet } - return site.name } var storeAddress: String { - SiteAddress().address + SiteAddress(siteSettings: siteSettings).address } @MainActor From 9bedfa105fd959665e4a0fdecb6989fbf23a15f4 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 26 Aug 2025 16:32:08 +0700 Subject: [PATCH 19/29] add PointOfSaleSettingsServiceTests --- .../Mocks/MockSettingStoreMethods.swift | 12 +- .../PointOfSaleSettingsServiceTests.swift | 166 ++++++++++++++++++ 2 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift diff --git a/Modules/Tests/YosemiteTests/Mocks/MockSettingStoreMethods.swift b/Modules/Tests/YosemiteTests/Mocks/MockSettingStoreMethods.swift index c204645d90b..16de26a26de 100644 --- a/Modules/Tests/YosemiteTests/Mocks/MockSettingStoreMethods.swift +++ b/Modules/Tests/YosemiteTests/Mocks/MockSettingStoreMethods.swift @@ -9,9 +9,12 @@ final class MockSettingStoreMethods: SettingStoreMethodsProtocol { var retrieveAnalyticsSettingCalled = false var enableAnalyticsSettingCalled = false var retrieveTaxBasedOnSettingCalled = false + var retrievePointOfSaleSettingsCalled = false var couponsEnabled: Bool = true var featureEnabled: Result = .success(true) + var retrievePointOfSaleSettingsResult: Result<[SiteSetting], Error> = .success([]) + var retrievePointOfSaleSettingsSiteID: Int64? func synchronizeGeneralSiteSettings(siteID: Int64, onCompletion: @escaping (Error?) -> Void) { @@ -74,6 +77,13 @@ final class MockSettingStoreMethods: SettingStoreMethodsProtocol { } func retrievePointOfSaleSettings(siteID: Int64) async throws -> [SiteSetting] { - [] + retrievePointOfSaleSettingsCalled = true + retrievePointOfSaleSettingsSiteID = siteID + switch retrievePointOfSaleSettingsResult { + case .success(let settings): + return settings + case .failure(let error): + throw error + } } } diff --git a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift new file mode 100644 index 00000000000..081bb733148 --- /dev/null +++ b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift @@ -0,0 +1,166 @@ +import Foundation +import Testing +@testable import Yosemite + +struct PointOfSaleSettingsServiceTests { + private let sut: PointOfSaleSettingsService + private let settingStoreMethods: MockSettingStoreMethods + private let storage: MockStorageManager + private let sampleSiteID: Int64 = 123 + + init() { + self.settingStoreMethods = MockSettingStoreMethods() + self.storage = MockStorageManager() + self.sut = PointOfSaleSettingsService(siteID: sampleSiteID, + settingStoreMethods: settingStoreMethods, + storage: storage) + } + + @Test func retrievePointOfSaleSettings_when_successful_then_returns_expected_settings() async throws { + // Given + let expectedSettings = makeSiteSettings() + settingStoreMethods.retrievePointOfSaleSettingsResult = .success(expectedSettings) + + // When + let settings = try await sut.retrievePointOfSaleSettings() + + // Then + #expect(settingStoreMethods.retrievePointOfSaleSettingsCalled) + #expect(settingStoreMethods.retrievePointOfSaleSettingsSiteID == sampleSiteID) + #expect(settings == expectedSettings) + } + + @Test func retrievePointOfSaleSettings_when_empty_settings_then_returns_empty_array() async throws { + // Given + settingStoreMethods.retrievePointOfSaleSettingsResult = .success([]) + + // When + let settings = try await sut.retrievePointOfSaleSettings() + + // Then + #expect(settingStoreMethods.retrievePointOfSaleSettingsCalled) + #expect(settingStoreMethods.retrievePointOfSaleSettingsSiteID == sampleSiteID) + #expect(settings.isEmpty) + } + + @Test func retrievePointOfSaleSettings_when_network_error_then_throws_error() async throws { + // Given + let expectedError = NSError(domain: "NetworkError", code: 500, userInfo: [NSLocalizedDescriptionKey: "Network request failed"]) + settingStoreMethods.retrievePointOfSaleSettingsResult = .failure(expectedError) + + // When + do { + _ = try await sut.retrievePointOfSaleSettings() + #expect(Bool(false), "Expected error to be thrown") + } catch { + // Then + #expect(settingStoreMethods.retrievePointOfSaleSettingsCalled) + #expect(settingStoreMethods.retrievePointOfSaleSettingsSiteID == sampleSiteID) + let nsError = error as NSError + #expect(nsError.domain == expectedError.domain) + #expect(nsError.code == expectedError.code) + } + } + + @Test func retrievePointOfSaleSettings_when_settingStoreMethods_throws_then_propagates_error() async throws { + // Given + let expectedError = TestError.customError + settingStoreMethods.retrievePointOfSaleSettingsResult = .failure(expectedError) + + // When + do { + _ = try await sut.retrievePointOfSaleSettings() + #expect(Bool(false), "Expected error to be thrown") + } catch { + // Then + #expect(settingStoreMethods.retrievePointOfSaleSettingsCalled) + #expect(settingStoreMethods.retrievePointOfSaleSettingsSiteID == sampleSiteID) + #expect(error as? TestError == expectedError) + } + } + + @Test func retrievePointOfSaleSettings_passes_correct_siteID_to_settingStoreMethods() async throws { + // Given + let differentSiteID: Int64 = 456 + let customSUT = PointOfSaleSettingsService(siteID: differentSiteID, + settingStoreMethods: settingStoreMethods, + storage: storage) + settingStoreMethods.retrievePointOfSaleSettingsResult = .success([]) + + // When + _ = try await customSUT.retrievePointOfSaleSettings() + + // Then + #expect(settingStoreMethods.retrievePointOfSaleSettingsCalled) + #expect(settingStoreMethods.retrievePointOfSaleSettingsSiteID == differentSiteID) + } + + @Test func retrievePointOfSaleSettings_with_complete_pos_settings_then_returns_all_settings() async throws { + // Given + let completeSettings = makeSiteSettings() + settingStoreMethods.retrievePointOfSaleSettingsResult = .success(completeSettings) + + // When + let settings = try await sut.retrievePointOfSaleSettings() + + // Then + #expect(settingStoreMethods.retrievePointOfSaleSettingsCalled) + #expect(settings.count == 5) + #expect(settings == completeSettings) + + // Verify specific settings + let storeNameSetting = settings.first { $0.settingID == "woocommerce_pos_store_name" } + #expect(storeNameSetting?.value == "WooCommerce Store") + + let addressSetting = settings.first { $0.settingID == "woocommerce_pos_store_address" } + #expect(addressSetting?.value == "123 Commerce Street\nBusiness District") + + let phoneSetting = settings.first { $0.settingID == "woocommerce_pos_store_phone" } + #expect(phoneSetting?.value == "+1 (555) 123-4567") + + let emailSetting = settings.first { $0.settingID == "woocommerce_pos_store_email" } + #expect(emailSetting?.value == "contact@store.com") + + let policySetting = settings.first { $0.settingID == "woocommerce_pos_refund_returns_policy" } + #expect(policySetting?.value == "30-day return policy with receipt") + } + + private func makeSiteSettings() -> [SiteSetting] { + return [ + SiteSetting(siteID: sampleSiteID, + settingID: "woocommerce_pos_store_name", + label: "Store Name", + settingDescription: "Name of the store for POS receipts", + value: "WooCommerce Store", + settingGroupKey: "pos"), + SiteSetting(siteID: sampleSiteID, + settingID: "woocommerce_pos_store_address", + label: "Store Address", + settingDescription: "Physical address for receipts", + value: "123 Commerce Street\nBusiness District", + settingGroupKey: "pos"), + SiteSetting(siteID: sampleSiteID, + settingID: "woocommerce_pos_store_phone", + label: "Store Phone", + settingDescription: "Contact phone number", + value: "+1 (555) 123-4567", + settingGroupKey: "pos"), + SiteSetting(siteID: sampleSiteID, + settingID: "woocommerce_pos_store_email", + label: "Store Email", + settingDescription: "Contact email address", + value: "contact@store.com", + settingGroupKey: "pos"), + SiteSetting(siteID: sampleSiteID, + settingID: "woocommerce_pos_refund_returns_policy", + label: "Refund & Returns Policy", + settingDescription: "Store policy for refunds and returns", + value: "30-day return policy with receipt", + settingGroupKey: "pos") + ] + } +} + +private enum TestError: Error, Equatable { + case customError +} From 50c3efe47039dc35d00456e7976db13a9adc9924 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 26 Aug 2025 17:03:30 +0700 Subject: [PATCH 20/29] make PointOfSaleSettingsServiceProtocol and add tests --- .../POS/PointOfSaleSettingsService.swift | 8 +- .../PointOfSaleSettingsController.swift | 5 +- .../WooCommerce.xcodeproj/project.pbxproj | 4 + .../PointOfSaleSettingsControllerTests.swift | 92 +++++++++++++++++++ 4 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleSettingsControllerTests.swift diff --git a/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift b/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift index 17333e1f72d..852a2544afb 100644 --- a/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift +++ b/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift @@ -1,9 +1,13 @@ - import Foundation import Networking import Storage -public final class PointOfSaleSettingsService { +public protocol PointOfSaleSettingsServiceProtocol { + var siteID: Int64 { get } + func retrievePointOfSaleSettings() async throws -> [SiteSetting] +} + +public final class PointOfSaleSettingsService: PointOfSaleSettingsServiceProtocol { public private(set) var siteID: Int64 private let settingStoreMethods: SettingStoreMethodsProtocol private let storage: StorageManagerType diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift index 3ceaa79654e..c1b85baa9dd 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift @@ -4,6 +4,7 @@ import enum Yosemite.Plugin import class Yosemite.PluginsService import Observation +import protocol Yosemite.PointOfSaleSettingsServiceProtocol import class Yosemite.PointOfSaleSettingsService import Storage @@ -50,10 +51,10 @@ class PointOfSaleSettingsPreviewController: PointOfSaleSettingsControllerProtoco private(set) var shouldShowReceiptInformation: Bool = false private let defaultSiteName: String? - private let settingsService: PointOfSaleSettingsService + private let settingsService: PointOfSaleSettingsServiceProtocol private let siteSettings: [SiteSetting] - init(settingsService: PointOfSaleSettingsService, + init(settingsService: PointOfSaleSettingsServiceProtocol, defaultSiteName: String? = ServiceLocator.stores.sessionManager.defaultSite?.name, siteSettings: [SiteSetting] = ServiceLocator.selectedSiteSettings.siteSettings) { self.settingsService = settingsService diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index ee8a4b8f160..29fa14a6eb4 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -1631,6 +1631,7 @@ 68D23B5B2E14FD1C00316BA6 /* SummaryTableViewCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D23B5A2E14FD1A00316BA6 /* SummaryTableViewCellViewModel.swift */; }; 68D3E98D2C7C371B005B6278 /* POSEdgeShadowViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D3E98C2C7C371B005B6278 /* POSEdgeShadowViewModifier.swift */; }; 68D5094E2AD39BC900B6FFD5 /* DiscountLineDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D5094D2AD39BC900B6FFD5 /* DiscountLineDetailsView.swift */; }; + 68D748102E5DB6D40048CFE9 /* PointOfSaleSettingsControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D7480F2E5DB6D20048CFE9 /* PointOfSaleSettingsControllerTests.swift */; }; 68D8FBD12BFEF9C700477C42 /* TotalsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D8FBD02BFEF9C700477C42 /* TotalsView.swift */; }; 68DF5A8D2CB38EEA000154C9 /* EditableOrderCouponLineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68DF5A8C2CB38EEA000154C9 /* EditableOrderCouponLineViewModel.swift */; }; 68DF5A8F2CB38F20000154C9 /* OrderCouponSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68DF5A8E2CB38F20000154C9 /* OrderCouponSectionView.swift */; }; @@ -4808,6 +4809,7 @@ 68D23B5A2E14FD1A00316BA6 /* SummaryTableViewCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SummaryTableViewCellViewModel.swift; sourceTree = ""; }; 68D3E98C2C7C371B005B6278 /* POSEdgeShadowViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSEdgeShadowViewModifier.swift; sourceTree = ""; }; 68D5094D2AD39BC900B6FFD5 /* DiscountLineDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscountLineDetailsView.swift; sourceTree = ""; }; + 68D7480F2E5DB6D20048CFE9 /* PointOfSaleSettingsControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleSettingsControllerTests.swift; sourceTree = ""; }; 68D8FBD02BFEF9C700477C42 /* TotalsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalsView.swift; sourceTree = ""; }; 68DF5A8C2CB38EEA000154C9 /* EditableOrderCouponLineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableOrderCouponLineViewModel.swift; sourceTree = ""; }; 68DF5A8E2CB38F20000154C9 /* OrderCouponSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCouponSectionView.swift; sourceTree = ""; }; @@ -8172,6 +8174,7 @@ children = ( 6818E7C02D93C76200677C16 /* PointOfSaleCouponsControllerTests.swift */, 20DB185C2CF5E7560018D3E1 /* PointOfSaleOrderControllerTests.swift */, + 68D7480F2E5DB6D20048CFE9 /* PointOfSaleSettingsControllerTests.swift */, 200BA15D2CF0A9EB0006DC5B /* PointOfSaleItemsControllerTests.swift */, 02E4F26F2E0F2C75003A31E7 /* POSEntryPointControllerTests.swift */, ); @@ -17242,6 +17245,7 @@ DED039272BC7934F005D0571 /* StoreStatsChartViewModelTests.swift in Sources */, 2024966A2B0CC97100EE527D /* MockWooPaymentsDepositService.swift in Sources */, DE61978D289A5326005E4362 /* WooSetupWebViewModelTests.swift in Sources */, + 68D748102E5DB6D40048CFE9 /* PointOfSaleSettingsControllerTests.swift in Sources */, DE7B17F92C1AA26B00A6C7D8 /* MockWooSubscriptionProductsEligibilityChecker.swift in Sources */, DE02ABAF2B5545C8008E0AC4 /* BlazeTargetLocationPickerViewModelTests.swift in Sources */, D802548726552E07001B2CC1 /* CardPresentModalNonRetryableErrorTests.swift in Sources */, diff --git a/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleSettingsControllerTests.swift b/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleSettingsControllerTests.swift new file mode 100644 index 00000000000..0e81a30408b --- /dev/null +++ b/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleSettingsControllerTests.swift @@ -0,0 +1,92 @@ + +import Testing +import Foundation +@testable import WooCommerce +@testable import Yosemite +import Storage + +// TODO: Expand controller tests in WOOMOB-1176 +struct PointOfSaleSettingsControllerTests { + private let mockSettingsService = MockPointOfSaleSettingsService() + private let mockStorageManager = MockStorageManager() + + @Test func storeName_when_defaultSiteName_provided_then_returns_defaultSiteName() async throws { + // Given + let expectedStoreName = "My Test Store" + let sut = PointOfSaleSettingsController(settingsService: mockSettingsService, + defaultSiteName: expectedStoreName, + siteSettings: []) + + // When + let actualStoreName = sut.storeName + + // Then + #expect(actualStoreName == expectedStoreName) + } + + @Test func storeName_when_defaultSiteName_nil_then_returns_notSet() async throws { + // Given + let sut = PointOfSaleSettingsController(settingsService: mockSettingsService, + defaultSiteName: nil, + siteSettings: []) + + // When + let actualStoreName = sut.storeName + + // Then + #expect(actualStoreName == "Not set") + } + + @Test func storeAddress_uses_injected_siteSettings() async throws { + // Given + let siteSettings = makeSampleSiteSettings() + let sut = PointOfSaleSettingsController(settingsService: mockSettingsService, + defaultSiteName: "Test Store", + siteSettings: siteSettings) + + // When + let storeAddress = sut.storeAddress + + // Then: address should be constructed from site settings, not empty + #expect(!storeAddress.isEmpty) + } + + private func makeSampleSiteSettings() -> [Yosemite.SiteSetting] { + return [ + SiteSetting(siteID: 123, + settingID: "woocommerce_store_address", + label: "Address", + settingDescription: "", + value: "123 Test Street", + settingGroupKey: "general"), + SiteSetting(siteID: 123, + settingID: "woocommerce_store_city", + label: "City", + settingDescription: "", + value: "Test City", + settingGroupKey: "general"), + SiteSetting(siteID: 123, + settingID: "woocommerce_default_country", + label: "Country", + settingDescription: "", + value: "US:CA", + settingGroupKey: "general") + ] + } +} + +private final class MockPointOfSaleSettingsService: PointOfSaleSettingsServiceProtocol { + var retrievePointOfSaleSettingsWasCalled = false + var retrievePointOfSaleSettingsResult: Result<[Yosemite.SiteSetting], Error> = .success([]) + let siteID: Int64 = 123 + + func retrievePointOfSaleSettings() async throws -> [Yosemite.SiteSetting] { + retrievePointOfSaleSettingsWasCalled = true + switch retrievePointOfSaleSettingsResult { + case .success(let settings): + return settings + case .failure(let error): + throw error + } + } +} From e2e84b24c0edee1885f326adc803208d11664265 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 26 Aug 2025 17:10:21 +0700 Subject: [PATCH 21/29] clean up unused storage --- .../Tools/POS/PointOfSaleSettingsService.swift | 11 +++-------- .../PointOfSale/PointOfSaleSettingsServiceTests.swift | 6 ++---- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift b/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift index 852a2544afb..3197c42528d 100644 --- a/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift +++ b/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift @@ -10,24 +10,19 @@ public protocol PointOfSaleSettingsServiceProtocol { public final class PointOfSaleSettingsService: PointOfSaleSettingsServiceProtocol { public private(set) var siteID: Int64 private let settingStoreMethods: SettingStoreMethodsProtocol - private let storage: StorageManagerType init(siteID: Int64, - settingStoreMethods: SettingStoreMethodsProtocol, - storage: StorageManagerType) { + settingStoreMethods: SettingStoreMethodsProtocol) { self.siteID = siteID self.settingStoreMethods = settingStoreMethods - self.storage = storage } public convenience init(siteID: Int64, credentials: Credentials?, storage: StorageManagerType) { let network = AlamofireNetwork(credentials: credentials) - self.init(siteID: siteID, - settingStoreMethods: SettingStoreMethods(storageManager: storage, - network: network), - storage: storage) + self.init(siteID: siteID, settingStoreMethods: SettingStoreMethods(storageManager: storage, + network: network)) } public func retrievePointOfSaleSettings() async throws -> [SiteSetting] { diff --git a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift index 081bb733148..6977e6e1717 100644 --- a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift +++ b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift @@ -12,8 +12,7 @@ struct PointOfSaleSettingsServiceTests { self.settingStoreMethods = MockSettingStoreMethods() self.storage = MockStorageManager() self.sut = PointOfSaleSettingsService(siteID: sampleSiteID, - settingStoreMethods: settingStoreMethods, - storage: storage) + settingStoreMethods: settingStoreMethods) } @Test func retrievePointOfSaleSettings_when_successful_then_returns_expected_settings() async throws { @@ -83,8 +82,7 @@ struct PointOfSaleSettingsServiceTests { // Given let differentSiteID: Int64 = 456 let customSUT = PointOfSaleSettingsService(siteID: differentSiteID, - settingStoreMethods: settingStoreMethods, - storage: storage) + settingStoreMethods: settingStoreMethods) settingStoreMethods.retrievePointOfSaleSettingsResult = .success([]) // When From 72bed3be6eddb2d895a4fe207fc6e768b1c4f2f1 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Tue, 26 Aug 2025 17:14:19 +0700 Subject: [PATCH 22/29] restore commented out preview --- .../POS/Presentation/Settings/PointOfSaleSettingsView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift index 5276b478cd5..41b237facfa 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift @@ -223,6 +223,6 @@ private extension PointOfSaleSettingsView { } } -//#Preview { -// PointOfSaleSettingsView() -//} +#Preview { + PointOfSaleSettingsView(settingsController: PointOfSaleSettingsPreviewController()) +} From 3b82a84a73b70062f71ab7fbab679acfd5c7fe1f Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Wed, 27 Aug 2025 10:12:59 +0700 Subject: [PATCH 23/29] remove unnecessary test --- .../PointOfSaleSettingsServiceTests.swift | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift index 6977e6e1717..de35f40548e 100644 --- a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift +++ b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift @@ -78,21 +78,6 @@ struct PointOfSaleSettingsServiceTests { } } - @Test func retrievePointOfSaleSettings_passes_correct_siteID_to_settingStoreMethods() async throws { - // Given - let differentSiteID: Int64 = 456 - let customSUT = PointOfSaleSettingsService(siteID: differentSiteID, - settingStoreMethods: settingStoreMethods) - settingStoreMethods.retrievePointOfSaleSettingsResult = .success([]) - - // When - _ = try await customSUT.retrievePointOfSaleSettings() - - // Then - #expect(settingStoreMethods.retrievePointOfSaleSettingsCalled) - #expect(settingStoreMethods.retrievePointOfSaleSettingsSiteID == differentSiteID) - } - @Test func retrievePointOfSaleSettings_with_complete_pos_settings_then_returns_all_settings() async throws { // Given let completeSettings = makeSiteSettings() From 61728b25ca9d62af535b9f472303bd792f96f711 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Wed, 27 Aug 2025 10:17:57 +0700 Subject: [PATCH 24/29] adjust access control --- .../Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift b/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift index 3197c42528d..3db603653cd 100644 --- a/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift +++ b/Modules/Sources/Yosemite/Tools/POS/PointOfSaleSettingsService.swift @@ -8,7 +8,7 @@ public protocol PointOfSaleSettingsServiceProtocol { } public final class PointOfSaleSettingsService: PointOfSaleSettingsServiceProtocol { - public private(set) var siteID: Int64 + public let siteID: Int64 private let settingStoreMethods: SettingStoreMethodsProtocol init(siteID: Int64, From 0398758272dcaa847b284f6adee5243e10489f77 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Wed, 27 Aug 2025 10:19:29 +0700 Subject: [PATCH 25/29] update var name --- .../Classes/POS/Models/PointOfSaleSettingsController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift index c1b85baa9dd..41184344472 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift @@ -66,7 +66,7 @@ class PointOfSaleSettingsPreviewController: PointOfSaleSettingsControllerProtoco if let defaultSiteName { return defaultSiteName } else { - return Localization.storeNotSet + return Localization.storeNameNotSet } } @@ -128,8 +128,8 @@ private extension PointOfSaleSettingsController { } enum Localization { - static let storeNotSet = NSLocalizedString( - "pointOfSaleSettingsService.storeNotSet", + static let storeNameNotSet = NSLocalizedString( + "pointOfSaleSettingsService.storeNameNotSet", value: "Not set", comment: "Text displayed on Point of Sale settings when store has not been provided." ) From 2e5c028b9f1803255bf8ea2b57fcccdeb5ddcb77 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Wed, 27 Aug 2025 10:20:24 +0700 Subject: [PATCH 26/29] merge tests --- .../PointOfSaleSettingsServiceTests.swift | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift index de35f40548e..88acef472ad 100644 --- a/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift +++ b/Modules/Tests/YosemiteTests/PointOfSale/PointOfSaleSettingsServiceTests.swift @@ -15,20 +15,6 @@ struct PointOfSaleSettingsServiceTests { settingStoreMethods: settingStoreMethods) } - @Test func retrievePointOfSaleSettings_when_successful_then_returns_expected_settings() async throws { - // Given - let expectedSettings = makeSiteSettings() - settingStoreMethods.retrievePointOfSaleSettingsResult = .success(expectedSettings) - - // When - let settings = try await sut.retrievePointOfSaleSettings() - - // Then - #expect(settingStoreMethods.retrievePointOfSaleSettingsCalled) - #expect(settingStoreMethods.retrievePointOfSaleSettingsSiteID == sampleSiteID) - #expect(settings == expectedSettings) - } - @Test func retrievePointOfSaleSettings_when_empty_settings_then_returns_empty_array() async throws { // Given settingStoreMethods.retrievePointOfSaleSettingsResult = .success([]) @@ -78,20 +64,20 @@ struct PointOfSaleSettingsServiceTests { } } - @Test func retrievePointOfSaleSettings_with_complete_pos_settings_then_returns_all_settings() async throws { + @Test func retrievePointOfSaleSettings_with_expected_pos_settings_then_returns_all_settings() async throws { // Given - let completeSettings = makeSiteSettings() - settingStoreMethods.retrievePointOfSaleSettingsResult = .success(completeSettings) + let expectedSettings = makeSiteSettings() + settingStoreMethods.retrievePointOfSaleSettingsResult = .success(expectedSettings) // When let settings = try await sut.retrievePointOfSaleSettings() // Then #expect(settingStoreMethods.retrievePointOfSaleSettingsCalled) + #expect(settingStoreMethods.retrievePointOfSaleSettingsSiteID == sampleSiteID) #expect(settings.count == 5) - #expect(settings == completeSettings) + #expect(settings == expectedSettings) - // Verify specific settings let storeNameSetting = settings.first { $0.settingID == "woocommerce_pos_store_name" } #expect(storeNameSetting?.value == "WooCommerce Store") From d036798b6ee70c6343703510bbd674c22bd2adfe Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Wed, 27 Aug 2025 10:30:50 +0700 Subject: [PATCH 27/29] make mock and wrap preview in debug flag --- .../PointOfSaleSettingsController.swift | 40 ++++++++++--------- .../Settings/PointOfSaleSettingsView.swift | 2 + .../PointOfSaleSettingsControllerTests.swift | 19 +++++++++ .../PointOfSaleAggregateModelTests.swift | 2 +- .../POSItemActionHandlerTests.swift | 2 +- 5 files changed, 44 insertions(+), 21 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift index 41184344472..b765fc1b959 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleSettingsController.swift @@ -22,25 +22,6 @@ protocol PointOfSaleSettingsControllerProtocol { func retrievePOSReceiptSettings() async } -class PointOfSaleSettingsPreviewController: PointOfSaleSettingsControllerProtocol { - var receiptStoreName: String? = "Sample Store" - var receiptStoreAddress: String? = "123 Main Street\nAnytown, ST 12345" - var receiptStorePhone: String? = "+1 (555) 123-4567" - var receiptStoreEmail: String? = "store@example.com" - var receiptRefundReturnsPolicy: String? = "30-day return policy" - var isLoading: Bool = false - var shouldShowReceiptInformation: Bool = true - var storeName: String = "Sample Store" - - var storeAddress: String { - "123 Main Street\nAnytown, ST 12345" - } - - func retrievePOSReceiptSettings() async { - // no-op - } -} - @Observable final class PointOfSaleSettingsController: PointOfSaleSettingsControllerProtocol { private(set) var receiptStoreName: String? private(set) var receiptStoreAddress: String? @@ -135,3 +116,24 @@ private extension PointOfSaleSettingsController { ) } } + +#if DEBUG +final class PointOfSaleSettingsPreviewController: PointOfSaleSettingsControllerProtocol { + var receiptStoreName: String? = "Sample Store" + var receiptStoreAddress: String? = "123 Main Street\nAnytown, ST 12345" + var receiptStorePhone: String? = "+1 (555) 123-4567" + var receiptStoreEmail: String? = "store@example.com" + var receiptRefundReturnsPolicy: String? = "30-day return policy" + var isLoading: Bool = false + var shouldShowReceiptInformation: Bool = true + var storeName: String = "Sample Store" + + var storeAddress: String { + "123 Main Street\nAnytown, ST 12345" + } + + func retrievePOSReceiptSettings() async { + // no-op + } +} +#endif diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift index 159808aa7a7..553eee313a6 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsView.swift @@ -178,6 +178,8 @@ private extension PointOfSaleSettingsView { } } +#if DEBUG #Preview { PointOfSaleSettingsView(settingsController: PointOfSaleSettingsPreviewController()) } +#endif diff --git a/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleSettingsControllerTests.swift b/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleSettingsControllerTests.swift index 0e81a30408b..d639dcf2792 100644 --- a/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleSettingsControllerTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Controllers/PointOfSaleSettingsControllerTests.swift @@ -90,3 +90,22 @@ private final class MockPointOfSaleSettingsService: PointOfSaleSettingsServicePr } } } + +final class MockPointOfSaleSettingsController: PointOfSaleSettingsControllerProtocol { + var receiptStoreName: String? = "Sample Store" + var receiptStoreAddress: String? = "123 Main Street\nAnytown, ST 12345" + var receiptStorePhone: String? = "+1 (555) 123-4567" + var receiptStoreEmail: String? = "store@example.com" + var receiptRefundReturnsPolicy: String? = "30-day return policy" + var isLoading: Bool = false + var shouldShowReceiptInformation: Bool = true + var storeName: String = "Sample Store" + + var storeAddress: String { + "123 Main Street\nAnytown, ST 12345" + } + + func retrievePOSReceiptSettings() async { + // no-op + } +} diff --git a/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift b/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift index 1db461366dd..ec9f4bbbbb7 100644 --- a/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Models/PointOfSaleAggregateModelTests.swift @@ -967,7 +967,7 @@ private func makePointOfSaleAggregateModel( couponsSearchController: PointOfSaleSearchingItemsControllerProtocol = MockPointOfSaleCouponsController(), cardPresentPaymentService: CardPresentPaymentFacade = MockCardPresentPaymentService(), orderController: PointOfSaleOrderControllerProtocol = MockPointOfSaleOrderController(), - settingsController: PointOfSaleSettingsControllerProtocol = PointOfSaleSettingsPreviewController(), + settingsController: PointOfSaleSettingsControllerProtocol = MockPointOfSaleSettingsController(), analytics: Analytics = WooAnalytics(analyticsProvider: MockAnalyticsProvider()), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking = MockPOSCollectOrderPaymentAnalyticsTracker(), searchHistoryService: POSSearchHistoryProviding = MockPOSSearchHistoryService(), diff --git a/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerTests.swift b/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerTests.swift index c15800b12b9..956b1049c05 100644 --- a/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerTests.swift +++ b/WooCommerce/WooCommerceTests/POS/Presentation/POSItemActionHandlerTests.swift @@ -101,7 +101,7 @@ private func makePointOfSaleAggregateModel( couponsSearchController: PointOfSaleSearchingItemsControllerProtocol = MockPointOfSaleCouponsController(), cardPresentPaymentService: CardPresentPaymentFacade = MockCardPresentPaymentService(), orderController: PointOfSaleOrderControllerProtocol = MockPointOfSaleOrderController(), - settingsController: PointOfSaleSettingsControllerProtocol = PointOfSaleSettingsPreviewController(), + settingsController: PointOfSaleSettingsControllerProtocol = MockPointOfSaleSettingsController(), collectOrderPaymentAnalyticsTracker: POSCollectOrderPaymentAnalyticsTracking = MockPOSCollectOrderPaymentAnalyticsTracker(), searchHistoryService: POSSearchHistoryProviding = MockPOSSearchHistoryService(), popularPurchasableItemsController: PointOfSaleItemsControllerProtocol = MockPointOfSaleItemsController(), From b7b3bee212e05ba95a687d31a710ed5b0ee227b0 Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Wed, 27 Aug 2025 10:32:17 +0700 Subject: [PATCH 28/29] update preview --- .../Settings/PointOfSaleSettingsStoreDetailView.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift index 8bed1b7a8b0..919ba1dd7c6 100644 --- a/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift +++ b/WooCommerce/Classes/POS/Presentation/Settings/PointOfSaleSettingsStoreDetailView.swift @@ -124,6 +124,8 @@ private extension PointOfSaleSettingsStoreDetailView { } } -//#Preview { -// PointOfSaleSettingsStoreDetailView(posSettingsService: PointOfSaleSettingsController()) -//} +#if DEBUG +#Preview { + PointOfSaleSettingsStoreDetailView(settingsController: PointOfSaleSettingsPreviewController()) +} +#endif From cf78712728346361995551fcbf3c1bd47e69f6fd Mon Sep 17 00:00:00 2001 From: iamgabrielma Date: Wed, 27 Aug 2025 10:37:29 +0700 Subject: [PATCH 29/29] remove unnecessary imports --- WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift | 2 -- .../Classes/POS/Presentation/PointOfSaleDashboardView.swift | 1 - 2 files changed, 3 deletions(-) diff --git a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift index 8abe90cebd2..8764976852b 100644 --- a/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift +++ b/WooCommerce/Classes/POS/Models/PointOfSaleAggregateModel.swift @@ -14,8 +14,6 @@ import enum Yosemite.POSItemType import protocol Yosemite.PointOfSaleBarcodeScanServiceProtocol import enum Yosemite.PointOfSaleBarcodeScanError -import class Yosemite.PointOfSaleSettingsService - protocol PointOfSaleAggregateModelProtocol { var orderStage: PointOfSaleOrderStage { get } diff --git a/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift b/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift index fd3c3cb51a8..c3220bbc68e 100644 --- a/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift +++ b/WooCommerce/Classes/POS/Presentation/PointOfSaleDashboardView.swift @@ -1,5 +1,4 @@ import SwiftUI -import class Yosemite.PointOfSaleSettingsService struct PointOfSaleDashboardView: View { @Environment(PointOfSaleAggregateModel.self) private var posModel