Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom fields: Entry point logic in Product Details #14068

Open
wants to merge 21 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a56dcf9
add "show custom fields" functionality on simple products.
hafizrahman Sep 28, 2024
3999821
Update logic for showing the Custom Fields option in Product details.
hafizrahman Sep 28, 2024
1092e0f
Move Custom Fields to the end for simple product actions.
hafizrahman Sep 29, 2024
61836a2
Add Custom Fields option to all other type of products.
hafizrahman Sep 29, 2024
72b669f
Add logic to hide custom fields option when creating new product.
hafizrahman Sep 29, 2024
9b6ffbc
Update custom fields option icon.
hafizrahman Sep 29, 2024
6fdc39d
Add visibility unit tests for custom fields option.
hafizrahman Sep 29, 2024
cd64bbc
Refactor to pass FeatureFlagService to the factory so that it's testa…
hafizrahman Sep 29, 2024
7076bde
Update visibility tests to include feature flag
hafizrahman Sep 29, 2024
f6f1c33
Refactor add new product case checking by using formType instead.
hafizrahman Sep 29, 2024
d6dcaf3
Move custom fields test about creation to ProductCreationTests
hafizrahman Sep 29, 2024
c1d5a44
Update unit tests with extra checks.
hafizrahman Sep 30, 2024
cfd6c47
Various design updates to make the navigation change to/from custom f…
hafizrahman Oct 1, 2024
1803039
Update test names
hafizrahman Oct 1, 2024
a8639e4
For custom fields visibility tests, loop through the entire product t…
hafizrahman Oct 1, 2024
722f978
Update requirement to only show custom fields row in product details …
hafizrahman Oct 1, 2024
3b45405
Add product details readonly tests related to custom fields row.
hafizrahman Oct 1, 2024
aa0ccd5
Fix strings for custom fields row title and subtitle
hafizrahman Oct 3, 2024
d513b54
Keep animation on push case.
hafizrahman Oct 3, 2024
fa038ce
Move CaseIterable conformance into unit test instead.
hafizrahman Oct 3, 2024
fbcabcd
Merge branch 'trunk' into feat/14067-show-custom-fields-product-details
hafizrahman Oct 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion WooCommerce/Classes/Extensions/UIImage+Woo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ extension UIImage {
/// Custom Fields Icon
///
static var customFieldsImage: UIImage {
return UIImage.gridicon(.alignLeft, size: CGSize(width: 24, height: 24))
return UIImage.gridicon(.grid, size: CGSize(width: 24, height: 24))
}

/// Print Icon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,12 +491,12 @@ extension OrderDetailsViewModel {
viewModel: CustomFieldsListViewModel(customFields: customFields),
onBackButtonTapped: {
// Restore the hidden navigation bar
viewController.navigationController?.setNavigationBarHidden(false, animated: true)
viewController.navigationController?.setNavigationBarHidden(false, animated: false)
})
)

// Hide the navigation bar as `CustomFieldsListView` will create its own toolbar.
viewController.navigationController?.setNavigationBarHidden(true, animated: true)
viewController.navigationController?.setNavigationBarHidden(true, animated: false)
viewController.navigationController?.pushViewController(customFieldsView, animated: true)
case .seeReceipt:
let countryCode = configurationLoader.configuration.countryCode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ struct CustomFieldsListView: View {
presentationMode.wrappedValue.dismiss()
}, label: {
Image(systemName: "chevron.backward")
.headlineLinkStyle()
})
}

Expand All @@ -64,9 +65,8 @@ struct CustomFieldsListView: View {
}
}
}
.wooNavigationBarStyle()
}
.wooNavigationBarStyle()
.navigationViewStyle(.stack)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ enum ProductFormBottomSheetAction {
case editLinkedProducts
case editReviews
case editDownloadableFiles
case editCustomFields

init?(productFormAction: ProductFormEditAction) {
switch productFormAction {
Expand All @@ -33,6 +34,8 @@ enum ProductFormBottomSheetAction {
self = .editReviews
case .downloadableFiles:
self = .editDownloadableFiles
case .customFields:
self = .editCustomFields
default:
return nil
}
Expand Down Expand Up @@ -69,6 +72,11 @@ extension ProductFormBottomSheetAction {
case .editDownloadableFiles:
return NSLocalizedString("Downloadable files",
comment: "Title of the product form bottom sheet action for editing downloadable files.")
case .editCustomFields:
return NSLocalizedString("productFormBottomSheetAction.customFieldsTitle",
value: "Custom Fields",
comment: "Title of the product form bottom sheet action for custom fields")

}
}

Expand Down Expand Up @@ -101,6 +109,11 @@ extension ProductFormBottomSheetAction {
case .editDownloadableFiles:
return NSLocalizedString("Include downloadable files with purchases",
comment: "Subtitle of the product form bottom sheet action for editing downloadable files.")

case .editCustomFields:
return NSLocalizedString("productFormBottomSheetAction.customFieldsSubtitle",
value: "View and edit custom fields",
comment: "Subtitle of the product form bottom sheet action for custom fields")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,8 @@ private extension DefaultProductFormTableViewModel {
func customFieldsRow() -> ProductFormSection.SettingsRow.ViewModel {
return ProductFormSection.SettingsRow.ViewModel(
icon: UIImage.customFieldsImage,
title: Localization.customFieldsTitle,
details: Localization.customFieldsDetails
title: Localization.customFieldsRowTitle,
details: Localization.customFieldsRowDetails
)
}

Expand Down Expand Up @@ -790,14 +790,14 @@ private extension DefaultProductFormTableViewModel.Localization {
comment: "Format of the sale period on the Price Settings row until a certain date")

// Custom fields
static let customFieldsTitle = NSLocalizedString(
"defaultProductFormTableViewModel.customFieldsDetails",
static let customFieldsRowTitle = NSLocalizedString(
"defaultProductFormTableViewModel.customFieldsRowTitle",
value: "Custom Fields",
comment: "Title for the Custom Fields row"
)

static let customFieldsDetails = NSLocalizedString(
"defaultProductFormTableViewModel.customFieldsDetails",
static let customFieldsRowDetails = NSLocalizedString(
"defaultProductFormTableViewModel.customFieldsRowDetails",
value: "View and edit the product's custom fields",
comment: "Details text for the Custom Fields row"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,34 +64,41 @@ struct ProductFormActionsFactory: ProductFormActionsFactoryProtocol {
private let variationsPrice: VariationsPrice

private let stores: StoresManager
private let featureFlagService: FeatureFlagService

private let isLinkedProductsPromoEnabled: Bool
private let linkedProductsPromoCampaign = LinkedProductsPromoCampaign()
private var linkedProductsPromoViewModel: FeatureAnnouncementCardViewModel {
.init(analytics: ServiceLocator.analytics,
configuration: linkedProductsPromoCampaign.configuration)
}
private let isCustomFieldsEnabled: Bool

private var isCustomFieldsEnabled: Bool {
/// Custom fields should only be available on .edit form type. For other cases:
/// - .add: The API requires product ID is required to save custom fields, so it can't be added during product creation.
/// - .readonly: Hide Custom Fields setting as it won't be useful in this context.
featureFlagService.isFeatureFlagEnabled(.viewEditCustomFieldsInProductsAndOrders)
&& formType == .edit
}

// TODO: Remove default parameter
init(product: EditableProductModel,
formType: ProductFormType,
canPromoteWithBlaze: Bool = false,
addOnsFeatureEnabled: Bool = true,
isLinkedProductsPromoEnabled: Bool = false,
isCustomFieldsEnabled: Bool =
ServiceLocator.featureFlagService.isFeatureFlagEnabled(.viewEditCustomFieldsInProductsAndOrders),
variationsPrice: VariationsPrice = .unknown,
stores: StoresManager = ServiceLocator.stores) {
stores: StoresManager = ServiceLocator.stores,
featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService) {
self.product = product
self.formType = formType
self.canPromoteWithBlaze = canPromoteWithBlaze
self.editable = formType != .readonly
self.addOnsFeatureEnabled = addOnsFeatureEnabled
self.variationsPrice = variationsPrice
self.isLinkedProductsPromoEnabled = isLinkedProductsPromoEnabled
self.isCustomFieldsEnabled = isCustomFieldsEnabled
self.stores = stores
self.featureFlagService = featureFlagService
}

/// Returns an array of actions that are visible in the product form primary section.
Expand Down Expand Up @@ -172,7 +179,6 @@ private extension ProductFormActionsFactory {

let actions: [ProductFormEditAction?] = [
.priceSettings(editable: editable, hideSeparator: false),
isCustomFieldsEnabled ? .customFields: nil,
shouldShowReviewsRow ? .reviews: nil,
shouldShowShippingSettingsRow ? .shippingSettings(editable: editable): nil,
.inventorySettings(editable: canEditInventorySettingsRow),
Expand All @@ -183,7 +189,8 @@ private extension ProductFormActionsFactory {
.downloadableFiles(editable: editable),
.shortDescription(editable: editable),
.linkedProducts(editable: editable),
.productType(editable: canEditProductType)
.productType(editable: canEditProductType),
isCustomFieldsEnabled ? .customFields: nil,
]
return actions.compactMap { $0 }
}
Expand All @@ -206,7 +213,8 @@ private extension ProductFormActionsFactory {
.tags(editable: editable),
.shortDescription(editable: editable),
.linkedProducts(editable: editable),
.productType(editable: canEditProductType)
.productType(editable: canEditProductType),
isCustomFieldsEnabled ? .customFields: nil
]
return actions.compactMap { $0 }
}
Expand All @@ -227,7 +235,8 @@ private extension ProductFormActionsFactory {
.tags(editable: editable),
.shortDescription(editable: editable),
.linkedProducts(editable: editable),
.productType(editable: canEditProductType)
.productType(editable: canEditProductType),
isCustomFieldsEnabled ? .customFields: nil
]
return actions.compactMap { $0 }
}
Expand Down Expand Up @@ -257,7 +266,8 @@ private extension ProductFormActionsFactory {
.tags(editable: editable),
.shortDescription(editable: editable),
.linkedProducts(editable: editable),
.productType(editable: canEditProductType)
.productType(editable: canEditProductType),
isCustomFieldsEnabled ? .customFields: nil
]
return actions.compactMap { $0 }
}
Expand All @@ -279,7 +289,8 @@ private extension ProductFormActionsFactory {
.tags(editable: editable),
.shortDescription(editable: editable),
.linkedProducts(editable: editable),
.productType(editable: false)
.productType(editable: false),
isCustomFieldsEnabled ? .customFields: nil
]
return actions.compactMap { $0 }
}
Expand All @@ -301,7 +312,8 @@ private extension ProductFormActionsFactory {
.tags(editable: editable),
.shortDescription(editable: editable),
.linkedProducts(editable: editable),
.productType(editable: false)
.productType(editable: false),
isCustomFieldsEnabled ? .customFields: nil
]
return actions.compactMap { $0 }
}
Expand All @@ -327,7 +339,8 @@ private extension ProductFormActionsFactory {
.downloadableFiles(editable: editable),
.shortDescription(editable: editable),
.linkedProducts(editable: editable),
.productType(editable: canEditProductType)
.productType(editable: canEditProductType),
isCustomFieldsEnabled ? .customFields: nil
]
return actions.compactMap { $0 }
}
Expand Down Expand Up @@ -359,7 +372,8 @@ private extension ProductFormActionsFactory {
.tags(editable: editable),
.shortDescription(editable: editable),
.linkedProducts(editable: editable),
.productType(editable: canEditProductType)
.productType(editable: canEditProductType),
isCustomFieldsEnabled ? .customFields: nil
]
}()

Expand All @@ -381,7 +395,8 @@ private extension ProductFormActionsFactory {
.tags(editable: editable),
.shortDescription(editable: editable),
.linkedProducts(editable: editable),
.productType(editable: false)
.productType(editable: false),
isCustomFieldsEnabled ? .customFields: nil
]
return actions.compactMap { $0 }
}
Expand All @@ -398,8 +413,7 @@ private extension ProductFormActionsFactory {
// The price settings action is always visible in the settings section.
return true
case .customFields:
// The custom fields action is always visible in the settings section.
return true
return product.product.customFields.isNotEmpty
case .subscriptionFreeTrial:
// The Free trial row is always visible in the settings section for a subscription product.
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Photos
import UIKit
import WordPressUI
import Yosemite
import SwiftUI
import protocol Storage.StorageManagerType

/// The entry UI for adding/editing a Product.
Expand Down Expand Up @@ -456,8 +457,7 @@ final class ProductFormViewController<ViewModel: ProductFormViewModelProtocol>:
eventLogger.logPriceSettingsTapped()
editPriceSettings()
case .customFields:
// TODO-13493: add tap handling.
return
showCustomFields()
case .reviews:
ServiceLocator.analytics.track(.productDetailViewReviewsTapped)
showReviews()
Expand Down Expand Up @@ -957,6 +957,8 @@ private extension ProductFormViewController {
case .editDownloadableFiles:
ServiceLocator.analytics.track(.productDetailViewDownloadableFilesTapped)
self?.showDownloadableFiles()
case .editCustomFields:
self?.showCustomFields()
}
}
}
Expand Down Expand Up @@ -1481,6 +1483,33 @@ private extension ProductFormViewController {
}
}

// MARK: Action - Show Custom Fields
private extension ProductFormViewController {
func showCustomFields() {
guard let product = product as? EditableProductModel else {
return
}

let customFields = product.product.customFields.map {
CustomFieldViewModel(metadata: $0)
}

let customFieldsView = UIHostingController(
rootView: CustomFieldsListView(
isEditable: true,
viewModel: CustomFieldsListViewModel(customFields: customFields),
onBackButtonTapped: { [weak self] in
// Restore the hidden navigation bar
self?.navigationController?.setNavigationBarHidden(false, animated: false)
})
)

// Hide the navigation bar as `CustomFieldsListView` will create its own toolbar.
navigationController?.setNavigationBarHidden(true, animated: false)
navigationController?.pushViewController(customFieldsView, animated: true)
}
}

// MARK: Action - Show Product Reviews Settings
//
private extension ProductFormViewController {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ struct MockFeatureFlagService: FeatureFlagService {
private let blazeEvergreenCampaigns: Bool
private let blazeCampaignObjective: Bool
private let revampedShippingLabelCreation: Bool
private let viewEditCustomFieldsInProductsAndOrders: Bool
private let favoriteProducts: Bool

init(isInboxOn: Bool = false,
Expand All @@ -40,6 +41,7 @@ struct MockFeatureFlagService: FeatureFlagService {
blazeEvergreenCampaigns: Bool = false,
blazeCampaignObjective: Bool = false,
revampedShippingLabelCreation: Bool = false,
viewEditCustomFieldsInProductsAndOrders: Bool = false,
favoriteProducts: Bool = false) {
self.isInboxOn = isInboxOn
self.isShowInboxCTAEnabled = isShowInboxCTAEnabled
Expand All @@ -59,6 +61,7 @@ struct MockFeatureFlagService: FeatureFlagService {
self.blazeEvergreenCampaigns = blazeEvergreenCampaigns
self.blazeCampaignObjective = blazeCampaignObjective
self.revampedShippingLabelCreation = revampedShippingLabelCreation
self.viewEditCustomFieldsInProductsAndOrders = viewEditCustomFieldsInProductsAndOrders
self.favoriteProducts = favoriteProducts
}

Expand Down Expand Up @@ -100,6 +103,8 @@ struct MockFeatureFlagService: FeatureFlagService {
return blazeCampaignObjective
case .revampedShippingLabelCreation:
return revampedShippingLabelCreation
case .viewEditCustomFieldsInProductsAndOrders:
return viewEditCustomFieldsInProductsAndOrders
case .favoriteProducts:
return favoriteProducts
default:
Expand Down
Loading