diff --git a/WooCommerce/Classes/ViewModels/Booking Details/AppointmentDetailsContent.swift b/WooCommerce/Classes/ViewModels/Booking Details/AppointmentDetailsContent.swift index ce4469ca44c..997a1785a96 100644 --- a/WooCommerce/Classes/ViewModels/Booking Details/AppointmentDetailsContent.swift +++ b/WooCommerce/Classes/ViewModels/Booking Details/AppointmentDetailsContent.swift @@ -1,6 +1,5 @@ import Foundation import struct Networking.Booking -import class WooFoundationCore.CurrencyFormatter extension BookingDetailsViewModel { struct AppointmentDetailsContent { @@ -34,7 +33,10 @@ extension BookingDetailsViewModel { to: booking.endDate ) ), - Row(title: Localization.appointmentDetailsPriceTitle, value: Self.formatPrice(booking.cost)) + Row( + title: Localization.appointmentDetailsPriceTitle, + value: BookingDetailsViewModel.formatPrice(for: booking, priceString: booking.cost) + ) ] } } @@ -51,15 +53,6 @@ private extension BookingDetailsViewModel.AppointmentDetailsContent { static func formatDuration(from startDate: Date, to endDate: Date) -> String { durationFormatter.string(from: startDate, to: endDate) ?? "" } - - static func formatPrice(_ price: String) -> String { - guard let decimalPrice = Decimal(string: price) else { - return price - } - return CurrencyFormatter( - currencySettings: ServiceLocator.currencySettings - ).formatAmount(decimalPrice) ?? price - } } private extension BookingDetailsViewModel.AppointmentDetailsContent { diff --git a/WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel+PriceFormatting.swift b/WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel+PriceFormatting.swift new file mode 100644 index 00000000000..d6de370a19b --- /dev/null +++ b/WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel+PriceFormatting.swift @@ -0,0 +1,31 @@ +import Foundation +import struct Networking.Booking +import class WooFoundationCore.CurrencyFormatter +import class WooFoundationCore.CurrencySettings +import enum WooFoundationCore.CurrencyCode + +extension BookingDetailsViewModel { + static func formatPrice(for booking: Booking, priceString: String) -> String { + guard let decimalPrice = Decimal(string: priceString) else { + return priceString + } + return CurrencyFormatter( + currencySettings: Self.currencySettings(for: booking) + ).formatAmount(decimalPrice) ?? priceString + } + + private static func currencySettings(for booking: Booking) -> CurrencySettings { + let siteCurrencySettings = ServiceLocator.currencySettings + guard let currencyCode = CurrencyCode(rawValue: booking.currency) else { + return siteCurrencySettings + } + + return CurrencySettings( + currencyCode: currencyCode, + currencyPosition: siteCurrencySettings.currencyPosition, + thousandSeparator: siteCurrencySettings.groupingSeparator, + decimalSeparator: siteCurrencySettings.decimalSeparator, + numberOfDecimals: siteCurrencySettings.fractionDigits + ) + } +} diff --git a/WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel.swift b/WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel.swift index fcc64ec24bc..1bd63cb6167 100644 --- a/WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel.swift +++ b/WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel.swift @@ -5,7 +5,11 @@ final class BookingDetailsViewModel: ObservableObject { let sections: [Section] let navigationTitle: String + private let booking: Booking + init(booking: Booking) { + self.booking = booking + navigationTitle = Self.navigationTitle(for: booking) let headerSection = Section.init( @@ -59,6 +63,27 @@ final class BookingDetailsViewModel: ObservableObject { } } +extension BookingDetailsViewModel { + var cancellationAlertMessage: String { + // Temporary hardcoded + //TODO: - replace with associated customer data + let productName = "Women's Haircut" + let customerName = "Margarita Nikolaevna" + + let date = booking.startDate.formatted( + date: .long, + time: .shortened + ) + + return String( + format: Localization.cancelBookingAlertMessage, + customerName, + productName, + date + ) + } +} + private extension BookingDetailsViewModel { static func navigationTitle(for booking: Booking) -> String { let titleFormat = NSLocalizedString( @@ -107,5 +132,11 @@ private extension BookingDetailsViewModel { value: "Booking notes", comment: "Header title for the 'Booking notes' section in the booking details screen." ) + + static let cancelBookingAlertMessage = NSLocalizedString( + "BookingDetailsView.cancelation.alert.message", + value: "%1$@ will no longer be able to attend “%2$@” on %3$@.", + comment: "Message for the booking cancellation confirmation alert. %1$@ is customer name, %2$@ is product name, %3$@ is booking date." + ) } } diff --git a/WooCommerce/Classes/ViewModels/Booking Details/PaymentContent.swift b/WooCommerce/Classes/ViewModels/Booking Details/PaymentContent.swift index a688d856f76..32cccfcb0d0 100644 --- a/WooCommerce/Classes/ViewModels/Booking Details/PaymentContent.swift +++ b/WooCommerce/Classes/ViewModels/Booking Details/PaymentContent.swift @@ -9,31 +9,21 @@ extension BookingDetailsViewModel { init(booking: Booking) { amounts = [ - .init(value: Self.formatPrice(booking.cost), type: .service), - .init(value: Self.formatPrice("0"), type: .tax), + .init(value: BookingDetailsViewModel.formatPrice(for: booking, priceString: booking.cost), type: .service), + .init(value: BookingDetailsViewModel.formatPrice(for: booking, priceString: "0"), type: .tax), .init(value: "-", type: .discount), - .init(value: Self.formatPrice(booking.cost), type: .total, emphasized: true), + .init(value: BookingDetailsViewModel.formatPrice(for: booking, priceString: booking.cost), type: .total, emphasized: true), ] actions = [ .markAsPaid, + .issueRefund, .viewOrder ] } } } -private extension BookingDetailsViewModel.PaymentContent { - static func formatPrice(_ price: String) -> String { - guard let decimalPrice = Decimal(string: price) else { - return price - } - return CurrencyFormatter( - currencySettings: ServiceLocator.currencySettings - ).formatAmount(decimalPrice) ?? price - } -} - extension BookingDetailsViewModel.PaymentContent { struct Amount { enum AmountType { @@ -83,7 +73,7 @@ extension BookingDetailsViewModel.PaymentContent.Amount.AmountType { extension BookingDetailsViewModel.PaymentContent { enum Action: String, Identifiable { case markAsPaid - case markAsRefunded + case issueRefund case viewOrder var id: String { @@ -97,8 +87,8 @@ extension BookingDetailsViewModel.PaymentContent.Action { switch self { case .markAsPaid: return Localization.paymentMarkAsPaidButtonTitle - case .markAsRefunded: - return Localization.paymentMarkAsRefundedButtonTitle + case .issueRefund: + return Localization.paymentIssueRefundButtonTitle case .viewOrder: return Localization.paymentViewOrderButtonTitle } @@ -108,7 +98,7 @@ extension BookingDetailsViewModel.PaymentContent.Action { switch self { case .markAsPaid: return true - case .markAsRefunded, .viewOrder: + case .issueRefund, .viewOrder: return false } } @@ -145,10 +135,10 @@ private enum Localization { comment: "Title for 'Mark as paid' button in payment section in booking details view." ) - static let paymentMarkAsRefundedButtonTitle = NSLocalizedString( - "BookingDetailsView.payment.markAsRefunded.title", - value: "Mark as refunded", - comment: "Title for 'Mark as refunded' button in payment section in booking details view." + static let paymentIssueRefundButtonTitle = NSLocalizedString( + "BookingDetailsView.payment.issueRefund.title", + value: "Issue refund", + comment: "Title for 'Issue refund' button in payment section in booking details view." ) static let paymentViewOrderButtonTitle = NSLocalizedString( diff --git a/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift b/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift index 4570b91a2c9..fda1604154c 100644 --- a/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift +++ b/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift @@ -6,6 +6,7 @@ struct BookingDetailsView: View { @Environment(\.dismiss) private var dismiss @State private var showingOptions = false @State private var showingStatusSheet = false + @State private var showingCancelAlert = false @ObservedObject private var viewModel: BookingDetailsViewModel @@ -78,6 +79,17 @@ struct BookingDetailsView: View { .presentationDetents([.medium, .large]) .presentationDragIndicator(.visible) } + .alert( + Localization.cancelBookingAlertTitle, + isPresented: $showingCancelAlert + ) { + Button(Localization.cancelBookingAlertCancelAction, role: .cancel) {} + Button(Localization.cancelBookingAlertConfirmAction, role: .destructive) { + print("On cancel booking confirmation tap") + } + } message: { + Text(viewModel.cancellationAlertMessage) + } } } @@ -185,7 +197,7 @@ private extension BookingDetailsView { } Button { - /// On cancel booking button tap + showingCancelAlert = true } label: { Text(Localization.cancelBooking) } @@ -356,6 +368,24 @@ private extension BookingDetailsView { comment: "'Cancel booking' button title in appointment details section in booking details view." ) + static let cancelBookingAlertTitle = NSLocalizedString( + "BookingDetailsView.cancelation.alert.title", + value: "Cancel booking", + comment: "Title for the booking cancellation confirmation alert." + ) + + static let cancelBookingAlertConfirmAction = NSLocalizedString( + "BookingDetailsView.cancelation.alert.confirmAction", + value: "Yes, cancel it", + comment: "Confirm button title for the booking cancellation confirmation alert." + ) + + static let cancelBookingAlertCancelAction = NSLocalizedString( + "BookingDetailsView.cancelation.alert.cancelAction", + value: "No, keep it", + comment: "Cancel button title for the booking cancellation confirmation alert." + ) + /// Attendance section static let statusRowTitle = NSLocalizedString( "BookingDetailsView.customer.status.title", diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 7c9fe8912b4..195c51dea08 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -984,6 +984,7 @@ 26FE09E124DB8FA000B9BDF5 /* SurveyCoordinatorControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26FE09E024DB8FA000B9BDF5 /* SurveyCoordinatorControllerTests.swift */; }; 26FFC50C2BED7C5A0067B3A4 /* WatchDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B249702BEC801400730730 /* WatchDependencies.swift */; }; 26FFC50D2BED7C5B0067B3A4 /* WatchDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26B249702BEC801400730730 /* WatchDependencies.swift */; }; + 2D05337E2E951A62004111FD /* BookingDetailsViewModel+PriceFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D05337D2E951A62004111FD /* BookingDetailsViewModel+PriceFormatting.swift */; }; 2D05D19F2E82D1A8004111FD /* BookingDetailsViewModel+Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D05D19E2E82D1A3004111FD /* BookingDetailsViewModel+Section.swift */; }; 2D05D1A22E82D235004111FD /* HeaderContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D05D1A12E82D233004111FD /* HeaderContent.swift */; }; 2D05D1A42E82D266004111FD /* AppointmentDetailsContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D05D1A32E82D25F004111FD /* AppointmentDetailsContent.swift */; }; @@ -3877,6 +3878,7 @@ 26FE09E024DB8FA000B9BDF5 /* SurveyCoordinatorControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyCoordinatorControllerTests.swift; sourceTree = ""; }; 26FFD32628C6A0A4002E5E5E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 26FFD32928C6A0F4002E5E5E /* UIImage+Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Widgets.swift"; sourceTree = ""; }; + 2D05337D2E951A62004111FD /* BookingDetailsViewModel+PriceFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BookingDetailsViewModel+PriceFormatting.swift"; sourceTree = ""; }; 2D05D19E2E82D1A3004111FD /* BookingDetailsViewModel+Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BookingDetailsViewModel+Section.swift"; sourceTree = ""; }; 2D05D1A02E82D1EF004111FD /* BookingDetailsViewModel+SectionContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BookingDetailsViewModel+SectionContent.swift"; sourceTree = ""; }; 2D05D1A12E82D233004111FD /* HeaderContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderContent.swift; sourceTree = ""; }; @@ -7944,6 +7946,7 @@ 2D05D1A62E82D49D004111FD /* BookingDetailsViewModel+Status.swift */, 2D05D19E2E82D1A3004111FD /* BookingDetailsViewModel+Section.swift */, 2D05D1A02E82D1EF004111FD /* BookingDetailsViewModel+SectionContent.swift */, + 2D05337D2E951A62004111FD /* BookingDetailsViewModel+PriceFormatting.swift */, ); path = "Booking Details"; sourceTree = ""; @@ -14919,6 +14922,7 @@ 45CE2D322625AA9A00E3CA00 /* ShippingLabelPackageList.swift in Sources */, AEFF77A42978389400667F7A /* PriceInputViewController.swift in Sources */, 02535CBB25823F7A00E137BB /* ShippingLabelPaperSize+UI.swift in Sources */, + 2D05337E2E951A62004111FD /* BookingDetailsViewModel+PriceFormatting.swift in Sources */, CCD2E67E25DD4DC900BD975D /* ProductVariationsViewModel.swift in Sources */, 02C2756824F4E77F00286C04 /* ProductShippingSettingsViewModel.swift in Sources */, E12AF69926BA8ADC00C371C1 /* CardPresentPaymentsOnboardingUseCase.swift in Sources */,