diff --git a/Modules/Sources/Networking/Model/Bookings/Booking.swift b/Modules/Sources/Networking/Model/Bookings/Booking.swift index a77dc822f08..65beafb8828 100644 --- a/Modules/Sources/Networking/Model/Bookings/Booking.swift +++ b/Modules/Sources/Networking/Model/Bookings/Booking.swift @@ -3,7 +3,7 @@ import Foundation /// Represents a Booking Entity. /// -public struct Booking: Codable, GeneratedCopiable, Equatable, GeneratedFakeable { +public struct Booking: Codable, GeneratedCopiable, Hashable, GeneratedFakeable { public let siteID: Int64 public let bookingID: Int64 public let allDay: Bool diff --git a/WooCommerce/Classes/Bookings/BookingList/BookingListContainerView.swift b/WooCommerce/Classes/Bookings/BookingList/BookingListContainerView.swift index 1968c355528..c955d393735 100644 --- a/WooCommerce/Classes/Bookings/BookingList/BookingListContainerView.swift +++ b/WooCommerce/Classes/Bookings/BookingList/BookingListContainerView.swift @@ -31,6 +31,10 @@ struct BookingListContainerView: View { } } } + .navigationDestination(for: Booking.self) { booking in + let viewModel = BookingDetailsViewModel(booking: booking) + BookingDetailsView(viewModel) + } } } diff --git a/WooCommerce/Classes/Bookings/BookingList/BookingListView.swift b/WooCommerce/Classes/Bookings/BookingList/BookingListView.swift index 4a06a35e38a..becea04fb26 100644 --- a/WooCommerce/Classes/Bookings/BookingList/BookingListView.swift +++ b/WooCommerce/Classes/Bookings/BookingList/BookingListView.swift @@ -37,7 +37,10 @@ private extension BookingListView { ScrollView { LazyVStack(spacing: 0) { ForEach(viewModel.bookings) { item in - bookingItem(item) + NavigationLink(value: item) { + bookingItem(item) + } + .buttonStyle(.plain) } InfiniteScrollIndicator(showContent: viewModel.shouldShowBottomActivityIndicator) diff --git a/WooCommerce/Classes/ViewModels/Booking Details/AppointmentDetailsContent.swift b/WooCommerce/Classes/ViewModels/Booking Details/AppointmentDetailsContent.swift index ce155cef0d3..ce4469ca44c 100644 --- a/WooCommerce/Classes/ViewModels/Booking Details/AppointmentDetailsContent.swift +++ b/WooCommerce/Classes/ViewModels/Booking Details/AppointmentDetailsContent.swift @@ -1,5 +1,6 @@ import Foundation import struct Networking.Booking +import class WooFoundationCore.CurrencyFormatter extension BookingDetailsViewModel { struct AppointmentDetailsContent { @@ -15,7 +16,6 @@ extension BookingDetailsViewModel { let rows: [Row] init(_ booking: Booking) { - let durationMinutes = Int(booking.endDate.timeIntervalSince(booking.startDate) / 60) let appointmentDate = booking.startDate.formatted(date: .numeric, time: .omitted) let appointmentTimeFrame = [ booking.startDate.formatted(date: .omitted, time: .shortened), @@ -27,13 +27,41 @@ extension BookingDetailsViewModel { Row(title: Localization.appointmentDetailsTimeRowTitle, value: appointmentTimeFrame), Row(title: Localization.appointmentDetailsAssignedStaffTitle, value: "Marianne Renoir"), /// Temporarily hardcoded Row(title: Localization.appointmentDetailsLocationTitle, value: "238 Willow Creek Drive, Montgomery ..."), /// Temporarily hardcoded - Row(title: Localization.appointmentDetailsDurationTitle, value: String(durationMinutes)), - Row(title: Localization.appointmentDetailsPriceTitle, value: booking.cost) + Row( + title: Localization.appointmentDetailsDurationTitle, + value: Self.formatDuration( + from: booking.startDate, + to: booking.endDate + ) + ), + Row(title: Localization.appointmentDetailsPriceTitle, value: Self.formatPrice(booking.cost)) ] } } } +private extension BookingDetailsViewModel.AppointmentDetailsContent { + static let durationFormatter: DateComponentsFormatter = { + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .short + formatter.allowedUnits = [.hour, .minute] + return formatter + }() + + 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 { enum Localization { static let appointmentDetailsDateRowTitle = NSLocalizedString( diff --git a/WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel.swift b/WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel.swift index 11c7beb62e7..fcc64ec24bc 100644 --- a/WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel.swift +++ b/WooCommerce/Classes/ViewModels/Booking Details/BookingDetailsViewModel.swift @@ -3,8 +3,11 @@ import struct Networking.Booking final class BookingDetailsViewModel: ObservableObject { let sections: [Section] + let navigationTitle: String init(booking: Booking) { + navigationTitle = Self.navigationTitle(for: booking) + let headerSection = Section.init( content: .header(HeaderContent(booking)) ) @@ -56,6 +59,17 @@ final class BookingDetailsViewModel: ObservableObject { } } +private extension BookingDetailsViewModel { + static func navigationTitle(for booking: Booking) -> String { + let titleFormat = NSLocalizedString( + "BookingDetailsView.navTitle", + value: "Booking #%1$d", + comment: "Booking Details screen nav bar title. %1$d is a placeholder for the booking ID." + ) + return String(format: titleFormat, booking.bookingID) + } +} + private extension BookingDetailsViewModel { enum Localization { static let appointmentDetailsSectionHeaderTitle = NSLocalizedString( diff --git a/WooCommerce/Classes/ViewModels/Booking Details/PaymentContent.swift b/WooCommerce/Classes/ViewModels/Booking Details/PaymentContent.swift index c8577638511..a688d856f76 100644 --- a/WooCommerce/Classes/ViewModels/Booking Details/PaymentContent.swift +++ b/WooCommerce/Classes/ViewModels/Booking Details/PaymentContent.swift @@ -1,5 +1,6 @@ import Foundation import struct Networking.Booking +import class WooFoundationCore.CurrencyFormatter extension BookingDetailsViewModel { struct PaymentContent { @@ -8,10 +9,10 @@ extension BookingDetailsViewModel { init(booking: Booking) { amounts = [ - .init(value: booking.cost, type: .service), - .init(value: "$0", type: .tax), + .init(value: Self.formatPrice(booking.cost), type: .service), + .init(value: Self.formatPrice("0"), type: .tax), .init(value: "-", type: .discount), - .init(value: "$55.00", type: .total, emphasized: true), + .init(value: Self.formatPrice(booking.cost), type: .total, emphasized: true), ] actions = [ @@ -22,6 +23,17 @@ extension BookingDetailsViewModel { } } +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 { diff --git a/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift b/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift index 7c160994206..8d33f66e49d 100644 --- a/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift +++ b/WooCommerce/Classes/ViewRelated/Bookings/Booking Details/BookingDetailsView.swift @@ -3,6 +3,7 @@ import Networking struct BookingDetailsView: View { @Environment(\.safeAreaInsets) var safeAreaInsets: EdgeInsets + @Environment(\.dismiss) private var dismiss @ObservedObject private var viewModel: BookingDetailsViewModel @@ -37,7 +38,26 @@ struct BookingDetailsView: View { print("Refresh triggered") } .navigationBarTitleDisplayMode(.inline) + .navigationTitle(viewModel.navigationTitle) .background(Color(uiColor: .systemGroupedBackground)) + .navigationBarBackButtonHidden(true) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button { + dismiss() + } label: { + Image(systemName: "chevron.backward") + } + } + ToolbarItem(placement: .navigationBarTrailing) { + Button { + //TODO: - present an action sheet + print("On ellipsis item tap") + } label: { + Image(systemName: "ellipsis") + } + } + } } }