diff --git a/WooCommerce/Classes/POS/Presentation/Coupons/POSCouponCreationSheet.swift b/WooCommerce/Classes/POS/Presentation/Coupons/POSCouponCreationSheet.swift new file mode 100644 index 00000000000..a0e29da615f --- /dev/null +++ b/WooCommerce/Classes/POS/Presentation/Coupons/POSCouponCreationSheet.swift @@ -0,0 +1,111 @@ +import SwiftUI +import struct Yosemite.Coupon +import enum Yosemite.POSItem +import struct Yosemite.POSCoupon + +extension View { + func posCouponCreationSheet( + isPresented: Binding, + onSuccess: @escaping (POSItem) -> Void + ) -> some View { + modifier(POSCouponCreationSheetModifier(isPresented: isPresented, onSuccess: onSuccess)) + } +} + +private struct POSCouponCreationSheetModifier: ViewModifier { + @Binding var isPresented: Bool + let onSuccess: (POSItem) -> Void + + @State private var selectedType: POSCouponDiscountType? + @State private var showCouponSelectionSheet: Bool = false + + func body(content: Content) -> some View { + content + .sheet(item: $selectedType) { (posDiscountType: POSCouponDiscountType) in + var addedCouponItem: POSItem? + let viewModel = AddEditCouponViewModel(discountType: posDiscountType.discountType, onSuccess: { coupon in + addedCouponItem = .coupon(.init(id: UUID(), code: coupon.code)) + }) + var view = AddEditCoupon(viewModel) + + view.dismissHandler = { + selectedType = nil + } + + view.onDisappear = { + if let couponItem = addedCouponItem { + selectedType = nil + onSuccess(couponItem) + addedCouponItem = nil + } + } + + view.discountTypeHandler = { _ in + showCouponSelectionSheet = true + } + + return view + .interactiveDismissDisabled() + .discountTypeSelectionSheet(isPresented: $showCouponSelectionSheet) { type in + showCouponSelectionSheet = false + viewModel.discountType = type.discountType + } + } + .discountTypeSelectionSheet(isPresented: $isPresented) { type in + selectedType = type + } + } +} + +private extension View { + func discountTypeSelectionSheet( + isPresented: Binding, + onSelection: @escaping (POSCouponDiscountType) -> Void + ) -> some View { + sheet(isPresented: isPresented) { + let command = DiscountTypeBottomSheetListSelectorCommand(selected: nil) { type in + onSelection(.init(discountType: type)) + } + + NavigationView { + BottomSheetListSelector( + viewProperties: BottomSheetListSelectorViewProperties(), + command: command, + onDismiss: { _ in + isPresented.wrappedValue = false + } + ) + .navigationBarTitleDisplayMode(.large) + .navigationTitle(Localization.selectCouponTypeTitle) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button(Localization.selectCouponCancelButtonTitle) { + isPresented.wrappedValue = false + } + } + } + } + .navigationViewStyle(.stack) + .interactiveDismissDisabled() + } + } +} + +private struct POSCouponDiscountType: Identifiable, Equatable { + var id: String { discountType.rawValue } + let discountType: Coupon.DiscountType +} + +private enum Localization { + static let selectCouponTypeTitle = NSLocalizedString( + "pos.couponCreationSheet.selectCoupon.title", + value: "Create coupon", + comment: "A title for the view that selects the type of coupon to create" + ) + + static let selectCouponCancelButtonTitle = NSLocalizedString( + "pos.couponCreationSheet.selectCoupon.cancel", + value: "Cancel", + comment: "A button that dismisses coupon creation sheet" + ) +} diff --git a/WooCommerce/Classes/POS/Presentation/ItemListView.swift b/WooCommerce/Classes/POS/Presentation/ItemListView.swift index a2f634ec93e..b169e6d9e9c 100644 --- a/WooCommerce/Classes/POS/Presentation/ItemListView.swift +++ b/WooCommerce/Classes/POS/Presentation/ItemListView.swift @@ -1,7 +1,6 @@ import SwiftUI import enum Yosemite.POSItem import protocol Yosemite.POSOrderableItem -import struct Yosemite.POSCoupon @available(iOS 17.0, *) struct ItemListView: View { @@ -23,6 +22,8 @@ struct ItemListView: View { @State private var selectedItemType: ItemType = .products + @State private var showCouponCreationModal: Bool = false + var body: some View { if #available(iOS 18.0, *) { NavigationStack { @@ -52,7 +53,19 @@ struct ItemListView: View { }, label: { Text("Coupons") }) + + Spacer() + + Button(action: { + showCouponCreationModal = true + }, label: { + Text(Image(systemName: "plus.circle.fill")) + }) + .font(.posButtonSymbolLarge) + .foregroundStyle(Color.posOnSurface) + .renderedIf(selectedItemType == .coupons) } + .padding(POSPadding.medium) .renderedIf(shouldShowCoupons) switch itemListState { @@ -76,6 +89,12 @@ struct ItemListView: View { .posModal(isPresented: $showSimpleProductsModal) { SimpleProductsOnlyInformation(isPresented: $showSimpleProductsModal) } + .posCouponCreationSheet(isPresented: $showCouponCreationModal, onSuccess: { couponItem in + Task { @MainActor in + posModel.addToCart(couponItem) + await posModel.refreshItems(base: .root) + } + }) } } diff --git a/WooCommerce/Classes/ViewRelated/Coupons/Add and Edit Coupons/AddEditCouponViewModel.swift b/WooCommerce/Classes/ViewRelated/Coupons/Add and Edit Coupons/AddEditCouponViewModel.swift index 877e30682fa..eb2c533bf33 100644 --- a/WooCommerce/Classes/ViewRelated/Coupons/Add and Edit Coupons/AddEditCouponViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Coupons/Add and Edit Coupons/AddEditCouponViewModel.swift @@ -213,7 +213,7 @@ final class AddEditCouponViewModel: ObservableObject { /// Init method for coupon creation /// - init(siteID: Int64, + init(siteID: Int64 = ServiceLocator.stores.sessionManager.defaultStoreID ?? 0, discountType: Coupon.DiscountType, stores: StoresManager = ServiceLocator.stores, storageManager: StorageManagerType = ServiceLocator.storageManager, diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 8bf627e1c38..a372faceb7c 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 015D99AA2C58C780001D7186 /* PointOfSaleCardPresentPaymentLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 015D99A92C58C780001D7186 /* PointOfSaleCardPresentPaymentLayout.swift */; }; 01620C4E2C5394B200D3EA2F /* POSProgressViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01620C4D2C5394B200D3EA2F /* POSProgressViewStyle.swift */; }; 01664F9E2C50E685007CB5DD /* POSFontStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01664F9D2C50E685007CB5DD /* POSFontStyle.swift */; }; + 016A77692D9D24B00004FCD6 /* POSCouponCreationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016A77682D9D24A70004FCD6 /* POSCouponCreationSheet.swift */; }; 016C6B972C74AB17000D86FD /* POSConnectivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016C6B962C74AB17000D86FD /* POSConnectivityView.swift */; }; 0174DDBB2CE5FD60005D20CA /* ReceiptEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0174DDBA2CE5FD5D005D20CA /* ReceiptEmailViewModel.swift */; }; 0174DDBF2CE600C5005D20CA /* ReceiptEmailViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0174DDBE2CE600C0005D20CA /* ReceiptEmailViewModelTests.swift */; }; @@ -3275,6 +3276,7 @@ 015D99A92C58C780001D7186 /* PointOfSaleCardPresentPaymentLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointOfSaleCardPresentPaymentLayout.swift; sourceTree = ""; }; 01620C4D2C5394B200D3EA2F /* POSProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSProgressViewStyle.swift; sourceTree = ""; }; 01664F9D2C50E685007CB5DD /* POSFontStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSFontStyle.swift; sourceTree = ""; }; + 016A77682D9D24A70004FCD6 /* POSCouponCreationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSCouponCreationSheet.swift; sourceTree = ""; }; 016C6B962C74AB17000D86FD /* POSConnectivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = POSConnectivityView.swift; sourceTree = ""; }; 0174DDBA2CE5FD5D005D20CA /* ReceiptEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptEmailViewModel.swift; sourceTree = ""; }; 0174DDBE2CE600C0005D20CA /* ReceiptEmailViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptEmailViewModelTests.swift; sourceTree = ""; }; @@ -6565,6 +6567,14 @@ path = "Reusable Views"; sourceTree = ""; }; + 016A77672D9D24A30004FCD6 /* Coupons */ = { + isa = PBXGroup; + children = ( + 016A77682D9D24A70004FCD6 /* POSCouponCreationSheet.swift */, + ); + path = Coupons; + sourceTree = ""; + }; 0174DDB92CE5FD49005D20CA /* ReceiptEmail */ = { isa = PBXGroup; children = ( @@ -7241,6 +7251,7 @@ 026826A12BF59DED0036F959 /* Presentation */ = { isa = PBXGroup; children = ( + 016A77672D9D24A30004FCD6 /* Coupons */, 026A50262D2F6BBF002C42C2 /* Infinite Scroll */, 029149792D2682DF00F7B3B3 /* Item Selector */, 01620C4C2C5394A400D3EA2F /* Reusable Views */, @@ -15817,6 +15828,7 @@ EE9D03122B89DF760077CED1 /* WooAnalyticsEvent+OrdersListFilter.swift in Sources */, 683421642ACE9391009021D7 /* ProductDiscountView.swift in Sources */, B6E851F3276320C70041D1BA /* RefundCustomAmountsDetailsViewModel.swift in Sources */, + 016A77692D9D24B00004FCD6 /* POSCouponCreationSheet.swift in Sources */, 024DF31F23743045006658FE /* Header+AztecFormatting.swift in Sources */, B5A8F8AD20B88D9900D211DE /* LoginPrologueViewController.swift in Sources */, B5D1AFC620BC7B7300DB0E8C /* StorePickerViewController.swift in Sources */,