Skip to content

Commit 7f4864e

Browse files
committed
Only show visible bottom sheet actions based on the product, and the opposite for the product form settings rows. Hide the more details CTA when no actions are available.
1 parent 14e4e64 commit 7f4864e

File tree

10 files changed

+278
-12
lines changed

10 files changed

+278
-12
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import Foundation
2+
3+
extension Optional where Wrapped == String {
4+
var isNilOrEmpty: Bool {
5+
guard let self = self else {
6+
return true
7+
}
8+
return self.isEmpty
9+
}
10+
}

WooCommerce/Classes/ViewRelated/Products/Edit Product/BottomSheetListSelector/ProductFormBottomSheetAction.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import Yosemite
23

34
/// Actions in the product form bottom sheet to add more product details.
45
enum ProductFormBottomSheetAction {
@@ -8,6 +9,23 @@ enum ProductFormBottomSheetAction {
89
case editBriefDescription
910
}
1011

12+
extension ProductFormBottomSheetAction {
13+
func isVisible(product: Product) -> Bool {
14+
switch self {
15+
case .editInventorySettings:
16+
let hasStockData = product.manageStock ? product.stockQuantity != nil: true
17+
return product.sku == nil && hasStockData == false
18+
case .editShippingSettings:
19+
return product.weight.isNilOrEmpty &&
20+
product.dimensions.height.isEmpty && product.dimensions.width.isEmpty && product.dimensions.length.isEmpty
21+
case .editCategories:
22+
return product.categories.isEmpty
23+
case .editBriefDescription:
24+
return product.briefDescription.isNilOrEmpty
25+
}
26+
}
27+
}
28+
1129
extension ProductFormBottomSheetAction {
1230
var title: String {
1331
switch self {

WooCommerce/Classes/ViewRelated/Products/Edit Product/BottomSheetListSelector/ProductFormBottomSheetListSelectorCommand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ final class ProductFormBottomSheetListSelectorCommand: BottomSheetListSelectorCo
2525
shouldShowCategoriesRow ? .editCategories: nil,
2626
.editBriefDescription
2727
]
28-
self.data = actions.compactMap({ $0 })
28+
self.data = actions.compactMap({ $0 }).filter({ $0.isVisible(product: product) })
2929
}
3030

3131
func configureCell(cell: ImageAndTitleAndTextTableViewCell, model: ProductFormBottomSheetAction) {

WooCommerce/Classes/ViewRelated/Products/Edit Product/DefaultProductFormTableViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ private extension DefaultProductFormTableViewModel {
6161
shouldShowBriefDescriptionRow ? .briefDescription(viewModel: briefDescriptionRow(product: product)): nil
6262
]
6363

64-
return rows.compactMap { $0 }
64+
return rows.compactMap { $0 }.filter { $0.isVisible(product: product) }
6565
}
6666
}
6767

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Foundation
2+
import Yosemite
3+
4+
extension ProductFormSection.SettingsRow {
5+
func isVisible(product: Product) -> Bool {
6+
guard let bottomSheetAction = bottomSheetAction else {
7+
// If there is no corresponding bottom sheet action, the settings row is visible by default.
8+
return true
9+
}
10+
return bottomSheetAction.isVisible(product: product) == false
11+
}
12+
13+
private var bottomSheetAction: ProductFormBottomSheetAction? {
14+
switch self {
15+
case .inventory:
16+
return .editInventorySettings
17+
case .shipping:
18+
return .editShippingSettings
19+
case .categories:
20+
return .editCategories
21+
case .briefDescription:
22+
return .editBriefDescription
23+
default:
24+
return nil
25+
}
26+
}
27+
}

WooCommerce/Classes/ViewRelated/Products/Edit Product/ProductFormViewController.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ final class ProductFormViewController: UIViewController {
3030
return
3131
}
3232

33+
updateMoreDetailsButtonVisibility(product: product)
34+
3335
viewModel = DefaultProductFormTableViewModel(product: product, currency: currency)
3436
tableViewDataSource = ProductFormTableViewDataSource(viewModel: viewModel,
3537
productImageStatuses: productImageActionHandler.productImageStatuses,
@@ -175,6 +177,8 @@ private extension ProductFormViewController {
175177
moreDetailsContainerView.pinSubviewToAllEdges(buttonContainerView)
176178
moreDetailsContainerView.setContentCompressionResistancePriority(.required, for: .vertical)
177179
moreDetailsContainerView.setContentHuggingPriority(.required, for: .vertical)
180+
181+
updateMoreDetailsButtonVisibility(product: product)
178182
}
179183
}
180184

@@ -207,6 +211,17 @@ private extension ProductFormViewController {
207211
let bottomSheet = BottomSheetViewController(childViewController: listSelectorViewController)
208212
bottomSheet.show(from: self, sourceView: button, arrowDirections: .down)
209213
}
214+
215+
func updateMoreDetailsButtonVisibility(product: Product) {
216+
guard featureFlagService.isFeatureFlagEnabled(.editProductsRelease2) else {
217+
moreDetailsContainerView.isHidden = true
218+
return
219+
}
220+
221+
let moreDetailsActions: [ProductFormBottomSheetAction] = [.editInventorySettings, .editShippingSettings, .editCategories, .editBriefDescription]
222+
let hasVisibleActions = moreDetailsActions.map({ $0.isVisible(product: product) }).contains(true)
223+
moreDetailsContainerView.isHidden = hasVisibleActions == false
224+
}
210225
}
211226

212227
// MARK: Navigation actions

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@
199199
02B296A722FA6DB500FD7A4C /* Date+StartAndEnd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B296A622FA6DB500FD7A4C /* Date+StartAndEnd.swift */; };
200200
02B296A922FA6E0000FD7A4C /* DateStartAndEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B296A822FA6E0000FD7A4C /* DateStartAndEndTests.swift */; };
201201
02B653AC2429F7BF00A9C839 /* MockTaxClassStoresManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B653AB2429F7BF00A9C839 /* MockTaxClassStoresManager.swift */; };
202+
02BA12852461674B008D8325 /* Optional+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02BA12842461674B008D8325 /* Optional+String.swift */; };
203+
02BA1287246167C7008D8325 /* ProductFormSection.SettingsRow+Visibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02BA1286246167C7008D8325 /* ProductFormSection.SettingsRow+Visibility.swift */; };
204+
02BA128B24616B48008D8325 /* ProductFormSectionSettingsRow+VisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02BA128A24616B48008D8325 /* ProductFormSectionSettingsRow+VisibilityTests.swift */; };
205+
02BA128D2461711D008D8325 /* ProductFormBottomSheetAction+VisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02BA128C2461711D008D8325 /* ProductFormBottomSheetAction+VisibilityTests.swift */; };
202206
02BA23C022EE9DAF009539E7 /* AsyncDictionaryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02BA23BF22EE9DAF009539E7 /* AsyncDictionaryTests.swift */; };
203207
02C0CD2A23B5BB1C00F880B1 /* ImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C0CD2923B5BB1C00F880B1 /* ImageService.swift */; };
204208
02C0CD2C23B5BC9600F880B1 /* DefaultImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C0CD2B23B5BC9600F880B1 /* DefaultImageService.swift */; };
@@ -1033,6 +1037,10 @@
10331037
02B296A622FA6DB500FD7A4C /* Date+StartAndEnd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+StartAndEnd.swift"; sourceTree = "<group>"; };
10341038
02B296A822FA6E0000FD7A4C /* DateStartAndEndTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateStartAndEndTests.swift; sourceTree = "<group>"; };
10351039
02B653AB2429F7BF00A9C839 /* MockTaxClassStoresManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTaxClassStoresManager.swift; sourceTree = "<group>"; };
1040+
02BA12842461674B008D8325 /* Optional+String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+String.swift"; sourceTree = "<group>"; };
1041+
02BA1286246167C7008D8325 /* ProductFormSection.SettingsRow+Visibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductFormSection.SettingsRow+Visibility.swift"; sourceTree = "<group>"; };
1042+
02BA128A24616B48008D8325 /* ProductFormSectionSettingsRow+VisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductFormSectionSettingsRow+VisibilityTests.swift"; sourceTree = "<group>"; };
1043+
02BA128C2461711D008D8325 /* ProductFormBottomSheetAction+VisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProductFormBottomSheetAction+VisibilityTests.swift"; sourceTree = "<group>"; };
10361044
02BA23BF22EE9DAF009539E7 /* AsyncDictionaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncDictionaryTests.swift; sourceTree = "<group>"; };
10371045
02C0CD2923B5BB1C00F880B1 /* ImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageService.swift; sourceTree = "<group>"; };
10381046
02C0CD2B23B5BC9600F880B1 /* DefaultImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultImageService.swift; sourceTree = "<group>"; };
@@ -1714,6 +1722,8 @@
17141722
02153210242376B5003F2BBD /* ProductPriceSettingsViewModelTests.swift */,
17151723
2611EE57243A46C500A74490 /* Categories */,
17161724
453770CF2431FEC400AC718D /* Settings */,
1725+
02BA128A24616B48008D8325 /* ProductFormSectionSettingsRow+VisibilityTests.swift */,
1726+
02BA128C2461711D008D8325 /* ProductFormBottomSheetAction+VisibilityTests.swift */,
17171727
);
17181728
path = "Edit Product";
17191729
sourceTree = "<group>";
@@ -1773,6 +1783,7 @@
17731783
025B1747237A92D800C780B4 /* ProductFormSection+ReusableTableRow.swift */,
17741784
025B1749237AA49D00C780B4 /* Product+ProductForm.swift */,
17751785
4580BA7123F192A300B5F764 /* Product Settings */,
1786+
02BA1286246167C7008D8325 /* ProductFormSection.SettingsRow+Visibility.swift */,
17761787
);
17771788
path = "Edit Product";
17781789
sourceTree = "<group>";
@@ -3400,6 +3411,7 @@
34003411
0215320A24231D5A003F2BBD /* UIStackView+Subviews.swift */,
34013412
029BFD4E24597D4B00FDDEEC /* UIButton+TitleAndImage.swift */,
34023413
57612988245888E2007BB2D9 /* NumberFormatter+LocalizedOrNinetyNinePlus.swift */,
3414+
02BA12842461674B008D8325 /* Optional+String.swift */,
34033415
);
34043416
path = Extensions;
34053417
sourceTree = "<group>";
@@ -4796,6 +4808,7 @@
47964808
74036CC0211B882100E462C2 /* PeriodDataViewController.swift in Sources */,
47974809
740382DB2267D94100A627F4 /* LargeImageTableViewCell.swift in Sources */,
47984810
CE22709F2293052700C0626C /* WebviewHelper.swift in Sources */,
4811+
02BA12852461674B008D8325 /* Optional+String.swift in Sources */,
47994812
744F00D221B582A9007EFA93 /* StarRatingView.swift in Sources */,
48004813
0282DD96233C960C006A5FDB /* SearchResultCell.swift in Sources */,
48014814
74AFF2EA211B9B1B0038153A /* StoreStatsViewController.swift in Sources */,
@@ -4830,6 +4843,7 @@
48304843
024DF31423742B7A006658FE /* AztecUnderlineFormatBarCommand.swift in Sources */,
48314844
CE32B10D20BEDE1C006FBCF4 /* TwoColumnSectionHeaderView.swift in Sources */,
48324845
D8D15F85230A18AB00D48B3F /* Analytics.swift in Sources */,
4846+
02BA1287246167C7008D8325 /* ProductFormSection.SettingsRow+Visibility.swift in Sources */,
48334847
D8C62471227AE0030011A7D6 /* SiteCountry.swift in Sources */,
48344848
B582F95920FFCEAA0060934A /* UITableViewHeaderFooterView+Helpers.swift in Sources */,
48354849
02C0CD2C23B5BC9600F880B1 /* DefaultImageService.swift in Sources */,
@@ -4882,12 +4896,14 @@
48824896
02FE89C7231FAA4100E85EF8 /* MainTabBarControllerTests+ProductListFeatureFlag.swift in Sources */,
48834897
B57C5C9F21B80E8300FF82B2 /* SampleError.swift in Sources */,
48844898
02404EE42315151400FF1170 /* MockupStatsVersionStoresManager.swift in Sources */,
4899+
02BA128D2461711D008D8325 /* ProductFormBottomSheetAction+VisibilityTests.swift in Sources */,
48854900
B53B898920D450AF00EDB467 /* SessionManagerTests.swift in Sources */,
48864901
5761298B24589B84007BB2D9 /* NumberFormatter+LocalizedOrNinetyNinePlusTests.swift in Sources */,
48874902
7435E59021C0162C00216F0F /* OrderNoteWooTests.swift in Sources */,
48884903
45F5A3C323DF31D2007D40E5 /* ShippingInputFormatterTests.swift in Sources */,
48894904
02153211242376B5003F2BBD /* ProductPriceSettingsViewModelTests.swift in Sources */,
48904905
45C8B25D231529410002FA77 /* CustomerInfoTableViewCellTests.swift in Sources */,
4906+
02BA128B24616B48008D8325 /* ProductFormSectionSettingsRow+VisibilityTests.swift in Sources */,
48914907
D85B8336222FCDA1002168F3 /* StatusListTableViewCellTests.swift in Sources */,
48924908
265D909D2446688C00D66F0F /* ProductCategoryViewModelBuilderTests.swift in Sources */,
48934909
B555531321B57E8800449E71 /* MockupUserNotificationsCenterAdapter.swift in Sources */,

WooCommerce/WooCommerceTests/Mockups/MockProduct.swift

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,17 @@ final class MockProduct {
99

1010
func product(downloadable: Bool = false,
1111
name: String = "Hogsmeade",
12+
briefDescription: String? = """
13+
[contact-form]\n<p>The green room&#8217;s max capacity is 30 people. Reserving the date / time of your event is free. \
14+
We can also accommodate large groups, with seating for 85 board game players at a time. If you have a large group, let us \
15+
know and we&#8217;ll send you our large group rate.</p>\n<p>GROUP RATES</p>\n<p>Reserve your event for up to 30 guests \
16+
for $100.</p>\n
17+
""",
1218
productShippingClass: ProductShippingClass? = nil,
1319
backordersSetting: ProductBackordersSetting = .notAllowed,
1420
productType: ProductType = .simple,
21+
manageStock: Bool = false,
22+
sku: String? = "",
1523
stockQuantity: Int? = nil,
1624
taxClass: String? = "",
1725
taxStatus: ProductTaxStatus = .taxable,
@@ -20,6 +28,8 @@ final class MockProduct {
2028
salePrice: String? = "",
2129
dateOnSaleStart: Date? = DateFormatter.Defaults.dateTimeFormatter.date(from: "2019-10-15T21:30:00"),
2230
dateOnSaleEnd: Date? = DateFormatter.Defaults.dateTimeFormatter.date(from: "2019-10-27T21:29:59"),
31+
dimensions: ProductDimensions = ProductDimensions(length: "0", width: "0", height: "0"),
32+
weight: String? = "213",
2333
variations: [Int64] = [],
2434
virtual: Bool = false,
2535
status: ProductStatus = .publish,
@@ -44,13 +54,8 @@ final class MockProduct {
4454
featured: featured,
4555
catalogVisibilityKey: catalogVisibility.rawValue,
4656
fullDescription: "<p>This is the party room!</p>\n",
47-
briefDescription: """
48-
[contact-form]\n<p>The green room&#8217;s max capacity is 30 people. Reserving the date / time of your event is free. \
49-
We can also accommodate large groups, with seating for 85 board game players at a time. If you have a large group, let us \
50-
know and we&#8217;ll send you our large group rate.</p>\n<p>GROUP RATES</p>\n<p>Reserve your event for up to 30 guests \
51-
for $100.</p>\n
52-
""",
53-
sku: "",
57+
briefDescription: briefDescription,
58+
sku: sku,
5459
price: "0",
5560
regularPrice: regularPrice,
5661
salePrice: salePrice,
@@ -65,15 +70,15 @@ final class MockProduct {
6570
externalURL: "http://somewhere.com",
6671
taxStatusKey: taxStatus.rawValue,
6772
taxClass: taxClass,
68-
manageStock: false,
73+
manageStock: manageStock,
6974
stockQuantity: stockQuantity,
7075
stockStatusKey: stockStatus.rawValue,
7176
backordersKey: backordersSetting.rawValue,
7277
backordersAllowed: false,
7378
backordered: false,
7479
soldIndividually: true,
75-
weight: "213",
76-
dimensions: ProductDimensions(length: "0", width: "0", height: "0"),
80+
weight: weight,
81+
dimensions: dimensions,
7782
shippingRequired: false,
7883
shippingTaxable: false,
7984
shippingClass: "",
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import XCTest
2+
@testable import WooCommerce
3+
@testable import Yosemite
4+
5+
final class ProductFormBottomSheetAction_VisibilityTests: XCTestCase {
6+
// MARK: - Inventory
7+
8+
func testInventoryActionIsVisibleForProductWithMissingInventoryData() {
9+
let product = Fixtures.productWithMissingInventoryData
10+
XCTAssertTrue(ProductFormBottomSheetAction.editInventorySettings.isVisible(product: product))
11+
}
12+
13+
func testInventoryRowIsInvisibleForProductWithMissingInventoryData() {
14+
let product = Fixtures.productWithInventoryData
15+
XCTAssertFalse(ProductFormBottomSheetAction.editInventorySettings.isVisible(product: product))
16+
}
17+
18+
// MARK: - Shipping
19+
20+
func testShippingActionIsVisibleForProductWithMissingShippingData() {
21+
let product = Fixtures.productWithMissingShippingData
22+
XCTAssertTrue(ProductFormBottomSheetAction.editShippingSettings.isVisible(product: product))
23+
}
24+
25+
func testShippingActionIsInvisibleForProductWithShippingData() {
26+
let product = Fixtures.productWithShippingData
27+
XCTAssertFalse(ProductFormBottomSheetAction.editShippingSettings.isVisible(product: product))
28+
}
29+
30+
// MARK: - Categories
31+
32+
func testCategoriesActionIsVisibleForProductWithoutCategories() {
33+
let product = Fixtures.productWithoutCategories
34+
XCTAssertTrue(ProductFormBottomSheetAction.editCategories.isVisible(product: product))
35+
}
36+
37+
func testCategoriesActionIsInvisibleForProductWithACategory() {
38+
let product = Fixtures.productWithOneCategory
39+
XCTAssertFalse(ProductFormBottomSheetAction.editCategories.isVisible(product: product))
40+
}
41+
42+
// MARK: - Brief description
43+
44+
func testBriefDescriptionActionIsVisibleForProductWithoutBriefDescription() {
45+
let product = Fixtures.productWithEmptyBriefDescription
46+
XCTAssertTrue(ProductFormBottomSheetAction.editBriefDescription.isVisible(product: product))
47+
}
48+
49+
func testBriefDescriptionActionIsInvisibleForProductWithNonEmptyBriefDescription() {
50+
let product = Fixtures.productWithNonEmptyBriefDescription
51+
XCTAssertFalse(ProductFormBottomSheetAction.editBriefDescription.isVisible(product: product))
52+
}
53+
}
54+
55+
private extension ProductFormBottomSheetAction_VisibilityTests {
56+
enum Fixtures {
57+
// Inventory
58+
static let productWithInventoryData = MockProduct().product(productType: .simple, manageStock: true, sku: "123")
59+
static let productWithMissingInventoryData = MockProduct().product(productType: .simple, manageStock: true, sku: nil, stockQuantity: nil)
60+
// Shipping
61+
static let productWithShippingData = MockProduct().product(productType: .simple,
62+
dimensions: ProductDimensions(length: "10", width: "0", height: "0"),
63+
weight: "100")
64+
static let productWithMissingShippingData = MockProduct().product(productType: .simple,
65+
dimensions: ProductDimensions(length: "", width: "", height: ""),
66+
weight: nil)
67+
// Categories
68+
static let productWithOneCategory = MockProduct().product(productType: .simple,
69+
categories: [ProductCategory(categoryID: 0, siteID: 0, parentID: 0, name: "", slug: "")])
70+
static let productWithoutCategories = MockProduct().product(productType: .simple, categories: [])
71+
// Brief description
72+
static let productWithNonEmptyBriefDescription = MockProduct().product(briefDescription: "desc", productType: .simple)
73+
static let productWithEmptyBriefDescription = MockProduct().product(briefDescription: "", productType: .simple)
74+
}
75+
}

0 commit comments

Comments
 (0)