diff --git a/WooCommerce/Classes/Tools/Notices/DefaultNoticePresenter.swift b/WooCommerce/Classes/Tools/Notices/DefaultNoticePresenter.swift index 2f0b4c55a6c..06261bc2b9e 100644 --- a/WooCommerce/Classes/Tools/Notices/DefaultNoticePresenter.swift +++ b/WooCommerce/Classes/Tools/Notices/DefaultNoticePresenter.swift @@ -380,7 +380,9 @@ private extension UNMutableNotificationContent { convenience init(notice: Notice) { self.init() - title = notice.notificationInfo?.title ?? notice.title + if let title = notice.notificationInfo?.title ?? notice.title { + self.title = title + } if let body = notice.notificationInfo?.body { self.body = body diff --git a/WooCommerce/Classes/Tools/Notices/Notice.swift b/WooCommerce/Classes/Tools/Notices/Notice.swift index 904d32470a0..a92cff3029e 100644 --- a/WooCommerce/Classes/Tools/Notices/Notice.swift +++ b/WooCommerce/Classes/Tools/Notices/Notice.swift @@ -9,7 +9,7 @@ struct Notice { /// The title that contains the reason for the notice /// - let title: String + let title: String? /// An optional subtitle that contains a secondary description of the reason for the notice /// @@ -35,7 +35,6 @@ struct Notice { /// let actionHandler: (() -> Void)? - /// Designated Initializer /// init(title: String, @@ -65,3 +64,41 @@ extension Notice: Equatable { lhs.actionTitle == rhs.actionTitle } } + +/// Convenience initializers to init without a`title` +/// These initializers help to ensure that at least one of title/subtitle/message is non-nil. +/// +extension Notice { + /// To init using `subtitle` and/or `message` + /// + init(subtitle: String, + message: String? = nil, + feedbackType: UINotificationFeedbackGenerator.FeedbackType? = nil, + notificationInfo: NoticeNotificationInfo? = nil, + actionTitle: String? = nil, + actionHandler: ((() -> Void))? = nil) { + self.title = nil + self.subtitle = subtitle + self.message = message + self.feedbackType = feedbackType + self.notificationInfo = notificationInfo + self.actionTitle = actionTitle + self.actionHandler = actionHandler + } + + /// To init using only `message` + /// + init(message: String, + feedbackType: UINotificationFeedbackGenerator.FeedbackType? = nil, + notificationInfo: NoticeNotificationInfo? = nil, + actionTitle: String? = nil, + actionHandler: ((() -> Void))? = nil) { + self.title = nil + self.subtitle = nil + self.message = message + self.feedbackType = feedbackType + self.notificationInfo = notificationInfo + self.actionTitle = actionTitle + self.actionHandler = actionHandler + } +} diff --git a/WooCommerce/Classes/View Modifiers/View+NoticesModifier.swift b/WooCommerce/Classes/View Modifiers/View+NoticesModifier.swift index fdec18ef195..b40e8b9fb0f 100644 --- a/WooCommerce/Classes/View Modifiers/View+NoticesModifier.swift +++ b/WooCommerce/Classes/View Modifiers/View+NoticesModifier.swift @@ -64,13 +64,15 @@ struct NoticeModifier: ViewModifier { Spacer() HStack(spacing: 0.0) { VStack { - HStack { - Text(notice.title) - .lineLimit(notice.message.isNilOrEmpty ? 0 : 2) - Spacer() + if let title = notice.title { + HStack { + Text(title) + .lineLimit(notice.message.isNilOrEmpty ? 0 : 2) + Spacer() + } + .font(Constants.titleFont) + .foregroundColor(Constants.titleColor) } - .font(Constants.titleFont) - .foregroundColor(Constants.titleColor) if let subtitle = notice.subtitle { HStack { Text(subtitle) @@ -81,7 +83,7 @@ struct NoticeModifier: ViewModifier { } if let message = notice.message { HStack { - Text(message) + BoldableTextView(message) Spacer() } .font(Constants.messageFont) diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/CollapsibleShipmentCardViewModel.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/CollapsibleShipmentCardViewModel.swift index 002cb367854..63a568f2e65 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/CollapsibleShipmentCardViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/CollapsibleShipmentCardViewModel.swift @@ -12,6 +12,18 @@ final class CollapsibleShipmentCardViewModel: ObservableObject, Identifiable { /// Child shipment rows, if the shipment has more than one quantity let childShipmentRows: [SelectableShipmentRowViewModel] + var onSelectionChange: (() -> Void)? + + var hasSelectedAnItem: Bool { + if mainShipmentRow.selected { + return true + } + + return childShipmentRows + .filter { $0.selected } + .isNotEmpty + } + init(parentShipmentId: String, childShipmentIds: [String], item: ShippingLabelPackageItem, @@ -47,6 +59,7 @@ private extension CollapsibleShipmentCardViewModel { guard let self else { return } childShipmentRows.forEach({ $0.setSelected(row.selected) }) + onSelectionChange?() } childShipmentRows.forEach({ @@ -54,6 +67,7 @@ private extension CollapsibleShipmentCardViewModel { guard let self else { return } mainShipmentRow.setSelected(false) + onSelectionChange?() } }) } diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/WooShippingSplitShipmentsDetailView.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/WooShippingSplitShipmentsDetailView.swift index e3565ddbc1a..50c1258f5ad 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/WooShippingSplitShipmentsDetailView.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/WooShippingSplitShipmentsDetailView.swift @@ -4,7 +4,7 @@ import Yosemite struct WooShippingSplitShipmentsDetailView: View { @Environment(\.dismiss) private var dismiss - let viewModel: WooShippingSplitShipmentsViewModel + @ObservedObject var viewModel: WooShippingSplitShipmentsViewModel var body: some View { NavigationView { @@ -41,6 +41,10 @@ struct WooShippingSplitShipmentsDetailView: View { } } } + .notice($viewModel.instructionsNotice, autoDismiss: false) + .onAppear { + viewModel.onAppear() + } } } diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/WooShippingSplitShipmentsViewModel.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/WooShippingSplitShipmentsViewModel.swift index 8e0c5a12c52..fd3d408e5ce 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/WooShippingSplitShipmentsViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShipping Split Shipments/WooShippingSplitShipmentsViewModel.swift @@ -26,10 +26,10 @@ final class WooShippingSplitShipmentsViewModel: ObservableObject { "\(itemsWeightLabel) • \(itemsPriceLabel)" } - @Published var selectedItemIDs: [String: [String]] = [:] - let shipmentCardViewModels: [CollapsibleShipmentCardViewModel] + @Published var instructionsNotice: Notice? + init(order: Order, config: WooShippingConfig, items: [ShippingLabelPackageItem], @@ -70,6 +70,11 @@ final class WooShippingSplitShipmentsViewModel: ObservableObject { }() configureSectionHeader() + configureSelectionCallback() + } + + func onAppear() { + showInstructionsNotice() } func selectAll() { @@ -80,6 +85,34 @@ final class WooShippingSplitShipmentsViewModel: ObservableObject { } private extension WooShippingSplitShipmentsViewModel { + func configureSelectionCallback() { + shipmentCardViewModels.forEach { viewModel in + viewModel.onSelectionChange = { [weak self] in + self?.checkSelectionAndHideInstructions() + } + } + } + + func showInstructionsNotice() { + if hasSelectedAnItem() == false { + instructionsNotice = Notice(message: Localization.SelectionInstructionsNotice.message, + feedbackType: .success, + actionTitle: Localization.SelectionInstructionsNotice.dismiss) { [weak self] in + self?.instructionsNotice = nil + } + } + } + + func checkSelectionAndHideInstructions() { + if hasSelectedAnItem() { + instructionsNotice = nil + } + } + + func hasSelectedAnItem() -> Bool { + shipmentCardViewModels.contains(where: { $0.hasSelectedAnItem }) + } + /// Configures the labels in the section header. /// func configureSectionHeader() { @@ -113,6 +146,16 @@ private extension WooShippingSplitShipmentsViewModel { // MARK: Constants private extension WooShippingSplitShipmentsViewModel { enum Localization { + enum SelectionInstructionsNotice { + static let message = NSLocalizedString("wooShipping.createLabels.splitShipment.SelectionInstructionsNotice.message", + value: "To split, select the items, and tap **move to new shipment** when the toolbar appears.", + comment: "Instructions to ask customer to select items to split during shipping label creation." + + " The content inside two double asterisks **...** denote bolded text.") + + static let dismiss = NSLocalizedString("wooShipping.createLabels.splitShipment.SelectionInstructionsNotice.dismiss", + value: "Dismiss", + comment: "Label of the button to dismiss the instructions notice in split shipments flow.") + } static func itemsCount(_ count: Decimal) -> String { return String.pluralize(count, singular: Localization.itemsCountSingularFormat, plural: Localization.itemsCountPluralFormat) } diff --git a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsView.swift b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsView.swift index 8feb1d61f4f..fb4d77559c3 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsView.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/Order Details/Shipping Labels/WooShipping Create Shipping Labels/WooShippingCreateLabelsView.swift @@ -104,7 +104,7 @@ private extension WooShippingCreateLabelsView { WooShippingPostPurchaseView(viewModel: postPurchase) } - if let splitShipmentsViewModel = viewModel.splitShipmentsViewModel { + if !viewModel.canViewLabel, let splitShipmentsViewModel = viewModel.splitShipmentsViewModel { WooShippingSplitShipmentsRow(viewModel: splitShipmentsViewModel) } diff --git a/WooCommerce/WooCommerceTests/Authentication/Epilogue/SwitchStoreNoticePresenterTests.swift b/WooCommerce/WooCommerceTests/Authentication/Epilogue/SwitchStoreNoticePresenterTests.swift index e7c18c79ee3..fff39804dc8 100644 --- a/WooCommerce/WooCommerceTests/Authentication/Epilogue/SwitchStoreNoticePresenterTests.swift +++ b/WooCommerce/WooCommerceTests/Authentication/Epilogue/SwitchStoreNoticePresenterTests.swift @@ -66,7 +66,8 @@ final class SwitchStoreNoticePresenterTests: XCTestCase { XCTAssertEqual(noticePresenter.queuedNotices.count, 1) let notice = try XCTUnwrap(noticePresenter.queuedNotices.first) - assertThat(notice.title, contains: siteName) + let title = try XCTUnwrap(notice.title) + assertThat(title, contains: siteName) let expectedTitle = String.localizedStringWithFormat(SwitchStoreNoticePresenter.Localization.titleFormat, site.name) XCTAssertEqual(notice.title, expectedTitle) } @@ -92,7 +93,8 @@ final class SwitchStoreNoticePresenterTests: XCTestCase { XCTAssertEqual(noticePresenter.queuedNotices.count, 1) let notice = try XCTUnwrap(noticePresenter.queuedNotices.first) - assertThat(notice.title, contains: siteName) + let title = try XCTUnwrap(notice.title) + assertThat(title, contains: siteName) let expectedTitle = String.localizedStringWithFormat(SwitchStoreNoticePresenter.Localization.titleFormat, site.name) XCTAssertEqual(notice.title, expectedTitle) }