From 060e707fa7d69358f0c5dc0ad0d7f96dae2f76a4 Mon Sep 17 00:00:00 2001 From: JoaoPinhoMinder Date: Thu, 13 Mar 2025 12:06:14 +0000 Subject: [PATCH] ALFMOB-158: Update PDP Button Copy Based on Item State - Added `productsPublisher` to both `WishlistService` and `BagService` to enable real-time updates of button titles based on item state. - Updated all relevant view models to observe `productsPublisher` instead of using the previous `getWishlistContent` and `getBagContent` methods. - Adjusted button titles dynamically to reflect the correct state (`"Add to Bag"` / `"Remove from Bag"` and `"Add to Wishlist"` / `"Remove from Wishlist"`). - Ensured that changing color resets both buttons, while changing size only resets the `"Add to Bag"` button (since wishlist state is unaffected by size). - Added necessary localized strings to `L10n` table. --- Alfie/Alfie/Views/BagView/BagView.swift | 3 - Alfie/Alfie/Views/BagView/BagViewModel.swift | 22 +++-- .../ProductDetails/ProductDetailsView.swift | 19 ++--- .../ProductDetailsViewModel.swift | 85 +++++++++++++++++-- .../ProductListingViewModel.swift | 20 +++-- .../Views/WishlistView/WishlistView.swift | 3 - .../WishlistView/WishlistViewModel.swift | 22 +++-- .../Core/Services/Bag/BagService.swift | 11 ++- .../Services/Wishlist/WishlistService.swift | 11 ++- .../Core/Features/MockBagViewModel.swift | 5 -- .../MockProductDetailsViewModel.swift | 2 + .../Mocks/Core/Services/MockBagService.swift | 11 ++- .../Core/Services/MockWishlistService.swift | 11 ++- .../Features/BagViewModelProtocol.swift | 1 - .../ProductDetailsViewModelProtocol.swift | 6 +- .../Features/WishlistViewModelProtocol.swift | 1 - .../Services/Bag/BagServiceProtocol.swift | 5 +- .../Wishlist/WishlistServiceProtocol.swift | 5 +- .../Localization/L10n+Generated.swift | 14 +++ .../Resources/Localization/L10n.xcstrings | 22 +++++ .../ProductDetailsViewModelTests.swift | 6 +- Alfie/Checksums/swiftgen_checksum.txt | 2 +- 22 files changed, 214 insertions(+), 73 deletions(-) diff --git a/Alfie/Alfie/Views/BagView/BagView.swift b/Alfie/Alfie/Views/BagView/BagView.swift index ebc2ae64..11ff37fc 100644 --- a/Alfie/Alfie/Views/BagView/BagView.swift +++ b/Alfie/Alfie/Views/BagView/BagView.swift @@ -38,9 +38,6 @@ struct BagView: View { .listRowSpacing(Spacing.space200) .padding(.vertical, Spacing.space200) .withToolbar(for: .tab(.bag)) - .onAppear { - viewModel.viewDidAppear() - } } } diff --git a/Alfie/Alfie/Views/BagView/BagViewModel.swift b/Alfie/Alfie/Views/BagView/BagViewModel.swift index bbb6d01f..d2cc3c95 100644 --- a/Alfie/Alfie/Views/BagView/BagViewModel.swift +++ b/Alfie/Alfie/Views/BagView/BagViewModel.swift @@ -1,26 +1,32 @@ +import Combine import Foundation import Models import SharedUI final class BagViewModel: BagViewModelProtocol { - @Published private(set) var products: [BagProduct] - + @Published private(set) var products: [BagProduct] = [] + private var subscriptions = Set() private let dependencies: BagDependencyContainer init(dependencies: BagDependencyContainer) { self.dependencies = dependencies - products = dependencies.bagService.getBagContent() - } - // MARK: - BagViewModelProtocol + setupBindigs() + } - func viewDidAppear() { - products = dependencies.bagService.getBagContent() + private func setupBindigs() { + dependencies.bagService.productsPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] bagProducts in + self?.products = bagProducts + } + .store(in: &subscriptions) } + // MARK: - BagViewModelProtocol + func didSelectDelete(for selectedProduct: BagProduct) { dependencies.bagService.removeProduct(selectedProduct) - products = dependencies.bagService.getBagContent() dependencies.analytics.trackRemoveFromBag(productID: selectedProduct.product.id) } diff --git a/Alfie/Alfie/Views/ProductDetails/ProductDetailsView.swift b/Alfie/Alfie/Views/ProductDetails/ProductDetailsView.swift index 3882c7ef..af2c93f9 100644 --- a/Alfie/Alfie/Views/ProductDetails/ProductDetailsView.swift +++ b/Alfie/Alfie/Views/ProductDetails/ProductDetailsView.swift @@ -181,7 +181,7 @@ struct ProductDetailsView: View { complementaryViews .padding(.horizontal, horizontalPadding) } - addToBag + bagButton } .fullScreenCover(isPresented: $isMediaFullScreen) { fullscreenMediaCarousel @@ -255,7 +255,7 @@ extension ProductDetailsView { } VStack { - addToBag + bagButton addToWishlist } .padding(.vertical, Spacing.space100) @@ -481,14 +481,11 @@ extension ProductDetailsView { } } - @ViewBuilder private var addToBag: some View { - if viewModel.shouldShow(section: .addToBag) { + @ViewBuilder private var bagButton: some View { + if viewModel.shouldShow(section: .bagButton) { VStack(spacing: Spacing.space0) { - let addToBagText = L10n.Product.AddToBag.Button.cta - let outOfStockText = L10n.Product.OutOfStock.Button.cta - ThemedButton( - text: viewModel.productHasStock ? addToBagText : outOfStockText, + text: viewModel.bagButtonTitle, isDisabled: .init( get: { !viewModel.isAddToBagEnabled }, set: { _ in } @@ -502,10 +499,10 @@ extension ProductDetailsView { } @ViewBuilder private var addToWishlist: some View { - if viewModel.shouldShow(section: .addToWishlist) { + if viewModel.shouldShow(section: .wishlistButton) { VStack(spacing: Spacing.space0) { ThemedButton( - text: L10n.Product.AddToWishlist.Button.cta, + text: viewModel.wishlistButtonTitle, style: .secondary, isFullWidth: true ) { @@ -608,7 +605,7 @@ private enum Constants { viewModel: MockProductDetailsViewModel( complementaryInfoToShow: [.paymentOptions, .returns], onShouldShowLoadingForSectionCalled: { _ in true }, - onShouldShowSectionCalled: { section in section != .addToBag } + onShouldShowSectionCalled: { section in section != .bagButton } ) ) .environmentObject(Coordinator()) diff --git a/Alfie/Alfie/Views/ProductDetails/ProductDetailsViewModel.swift b/Alfie/Alfie/Views/ProductDetails/ProductDetailsViewModel.swift index ef6a9f49..9588d641 100644 --- a/Alfie/Alfie/Views/ProductDetails/ProductDetailsViewModel.swift +++ b/Alfie/Alfie/Views/ProductDetails/ProductDetailsViewModel.swift @@ -18,6 +18,7 @@ final class ProductDetailsViewModel: ProductDetailsViewModelProtocol { private(set) var sizingSelectionConfiguration: ColorAndSizingSelectorConfiguration = .init(items: []) public let productId: String private let initialSelectedProduct: SelectedProduct? + private var subscriptions = Set() private var product: Product? { guard case .success(let model) = state else { @@ -100,6 +101,9 @@ final class ProductDetailsViewModel: ProductDetailsViewModelProtocol { !sizingSelectionConfiguration.items.isEmpty ? selectedVariant?.size != nil : true } + @Published var bagButtonTitle: String = "" + @Published var wishlistButtonTitle: String = "" + init( configuration: ProductDetailsConfiguration, dependencies: ProductDetailsDependencyContainer @@ -132,6 +136,59 @@ final class ProductDetailsViewModel: ProductDetailsViewModelProtocol { selectedVariant: selectedProduct.selectedVariant ) } + + setupBindings() + } + + private func setupBindings() { + Publishers.CombineLatest( + $state, + dependencies.bagService.productsPublisher + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.updateBagButtonTitle() + } + .store(in: &subscriptions) + + Publishers.CombineLatest( + $state, + dependencies.wishlistService.productsPublisher + ) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.updateWishlistButtonTitle() + } + .store(in: &subscriptions) + } + + private func updateBagButtonTitle() { + guard productHasStock else { + bagButtonTitle = L10n.Product.OutOfStock.Button.cta + return + } + + guard let selectedProduct else { + bagButtonTitle = L10n.Product.AddToBag.Button.cta + return + } + + let bagProduct = BagProduct(selectedProduct: selectedProduct) + bagButtonTitle = dependencies.bagService.containsProduct(bagProduct) + ? L10n.Product.RemoveFromBag.Button.cta + : L10n.Product.AddToBag.Button.cta + } + + private func updateWishlistButtonTitle() { + guard let selectedProduct else { + wishlistButtonTitle = L10n.Product.AddToWishlist.Button.cta + return + } + + let wishlistProduct = WishlistProduct(selectedProduct: selectedProduct) + wishlistButtonTitle = dependencies.wishlistService.containsProduct(wishlistProduct) + ? L10n.Product.RemoveFromWishlist.Button.cta + : L10n.Product.AddToWishlist.Button.cta } func viewDidAppear() { @@ -150,8 +207,8 @@ final class ProductDetailsViewModel: ProductDetailsViewModelProtocol { .complementaryInfo: return state.isLoading case .productDescription, - .addToBag, // swiftlint:disable:this indentation_width - .addToWishlist: + .bagButton, // swiftlint:disable:this indentation_width + .wishlistButton: return false } } @@ -169,9 +226,9 @@ final class ProductDetailsViewModel: ProductDetailsViewModelProtocol { return state.isLoading || !productImageUrls.isEmpty case .productDescription: return !productDescription.isEmpty - case .addToBag: + case .bagButton: return state.isSuccess - case .addToWishlist: + case .wishlistButton: return state.isSuccess && dependencies.configurationService.isFeatureEnabled(.wishlist) } // swiftlint:enable vertical_whitespace_between_cases @@ -197,15 +254,27 @@ final class ProductDetailsViewModel: ProductDetailsViewModelProtocol { func didTapAddToBag() { guard let selectedProduct else { return } let bagProduct = BagProduct(selectedProduct: selectedProduct) - dependencies.bagService.addProduct(bagProduct) - dependencies.analytics.trackAddToBag(productID: bagProduct.id) + + if dependencies.bagService.containsProduct(bagProduct) { + dependencies.bagService.removeProduct(bagProduct) + dependencies.analytics.trackRemoveFromBag(productID: bagProduct.id) + } else { + dependencies.bagService.addProduct(bagProduct) + dependencies.analytics.trackAddToBag(productID: bagProduct.id) + } } func didTapAddToWishlist() { guard let selectedProduct else { return } let wishlistProduct = WishlistProduct(selectedProduct: selectedProduct) - dependencies.wishlistService.addProduct(wishlistProduct) - dependencies.analytics.trackAddToWishlist(productID: wishlistProduct.id) + + if dependencies.wishlistService.containsProduct(wishlistProduct) { + dependencies.wishlistService.removeProduct(wishlistProduct) + dependencies.analytics.trackRemoveFromWishlist(productID: wishlistProduct.id) + } else { + dependencies.wishlistService.addProduct(wishlistProduct) + dependencies.analytics.trackAddToWishlist(productID: wishlistProduct.id) + } } func colorSwatches(filteredBy searchTerm: String) -> [ColorSwatch] { diff --git a/Alfie/Alfie/Views/ProductListing/ProductListingViewModel.swift b/Alfie/Alfie/Views/ProductListing/ProductListingViewModel.swift index 1de03bee..c4c580de 100644 --- a/Alfie/Alfie/Views/ProductListing/ProductListingViewModel.swift +++ b/Alfie/Alfie/Views/ProductListing/ProductListingViewModel.swift @@ -14,8 +14,9 @@ final class ProductListingViewModel: ProductListingViewModelProtocol { @Published var style: ProductListingListStyle @Published var showRefine = false @Published var sortOption: String? - @Published private(set) var wishlistContent: [SelectedProduct] + @Published private(set) var wishlistContent: [SelectedProduct] = [] @Published private(set) var state: PaginatedViewState + private var subscriptions = Set() private enum Constants { static let defaultSkeletonItemsSize = 12 @@ -54,11 +55,20 @@ final class ProductListingViewModel: ProductListingViewModelProtocol { sortOption = sort query = searchText ?? urlQueryParameters.map(\.values)?.joined(separator: ",") state = .loadingFirstPage(.init(title: "", products: .skeleton(itemsSize: skeletonItemsSize))) - wishlistContent = dependencies.wishlistService.getWishlistContent() + + setupBindings() + } + + private func setupBindings() { + dependencies.wishlistService.productsPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] wishListProducts in + self?.wishlistContent = wishListProducts + } + .store(in: &subscriptions) } func viewDidAppear() { - wishlistContent = dependencies.wishlistService.getWishlistContent() Task { await loadProductsIfNeeded() } @@ -79,7 +89,8 @@ final class ProductListingViewModel: ProductListingViewModelProtocol { func didSelect(_: Product) {} func isFavoriteState(for product: Product) -> Bool { - wishlistContent.contains { $0.product.id == product.id } + let wishlistProduct = WishlistProduct(product: product) + return dependencies.wishlistService.containsProduct(wishlistProduct) } func didTapAddToWishlist(for product: Product, isFavorite: Bool) { @@ -92,7 +103,6 @@ final class ProductListingViewModel: ProductListingViewModelProtocol { dependencies.wishlistService.removeProductVariants(wishlistProduct) dependencies.analytics.trackRemoveFromWishlist(productID: wishlistProduct.id) } - wishlistContent = dependencies.wishlistService.getWishlistContent() } func didApplyFilters() { diff --git a/Alfie/Alfie/Views/WishlistView/WishlistView.swift b/Alfie/Alfie/Views/WishlistView/WishlistView.swift index e2a3dcaf..1551ff75 100644 --- a/Alfie/Alfie/Views/WishlistView/WishlistView.swift +++ b/Alfie/Alfie/Views/WishlistView/WishlistView.swift @@ -40,9 +40,6 @@ struct WishlistView: View { .padding(.horizontal, Spacing.space200) } .padding(.vertical, Spacing.space200) - .onAppear { - viewModel.viewDidAppear() - } } } diff --git a/Alfie/Alfie/Views/WishlistView/WishlistViewModel.swift b/Alfie/Alfie/Views/WishlistView/WishlistViewModel.swift index 4591040d..c9826cac 100644 --- a/Alfie/Alfie/Views/WishlistView/WishlistViewModel.swift +++ b/Alfie/Alfie/Views/WishlistView/WishlistViewModel.swift @@ -1,27 +1,33 @@ +import Combine import Foundation import Models import SharedUI final class WishlistViewModel: WishlistViewModelProtocol { - @Published private(set) var products: [WishlistProduct] - + @Published private(set) var products: [WishlistProduct] = [] + private var subscriptions = Set() private let dependencies: WishlistDependencyContainer init(dependencies: WishlistDependencyContainer) { self.dependencies = dependencies - products = dependencies.wishlistService.getWishlistContent() - } - // MARK: - WishListViewModelProtocol + setupBindigs() + } - func viewDidAppear() { - products = dependencies.wishlistService.getWishlistContent() + private func setupBindigs() { + dependencies.wishlistService.productsPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] wishListProducts in + self?.products = wishListProducts + } + .store(in: &subscriptions) } + // MARK: - WishListViewModelProtocol + func didSelectDelete(for wishlistProduct: WishlistProduct) { dependencies.wishlistService.removeProduct(wishlistProduct) dependencies.analytics.trackRemoveFromWishlist(productID: wishlistProduct.product.id) - products = dependencies.wishlistService.getWishlistContent() } func productCardViewModel(for wishlistProduct: WishlistProduct) -> VerticalProductCardViewModel { diff --git a/Alfie/AlfieKit/Sources/Core/Services/Bag/BagService.swift b/Alfie/AlfieKit/Sources/Core/Services/Bag/BagService.swift index 9cb0b32c..7df7a2e9 100644 --- a/Alfie/AlfieKit/Sources/Core/Services/Bag/BagService.swift +++ b/Alfie/AlfieKit/Sources/Core/Services/Bag/BagService.swift @@ -1,9 +1,14 @@ +import Combine import Foundation import Models // TODO: Update with an actual implementation with storage public final class BagService: BagServiceProtocol { - private var products: [BagProduct] = [] + @Published private var products: [BagProduct] = [] + + public var productsPublisher: AnyPublisher<[BagProduct], Never> { + $products.eraseToAnyPublisher() + } public init() { } @@ -17,7 +22,7 @@ public final class BagService: BagServiceProtocol { products = products.filter { $0.id != bagProduct.id } } - public func getBagContent() -> [BagProduct] { - products + public func containsProduct(_ bagProduct: BagProduct) -> Bool { + products.contains { $0.id == bagProduct.id } } } diff --git a/Alfie/AlfieKit/Sources/Core/Services/Wishlist/WishlistService.swift b/Alfie/AlfieKit/Sources/Core/Services/Wishlist/WishlistService.swift index 27f3ea98..8040c915 100644 --- a/Alfie/AlfieKit/Sources/Core/Services/Wishlist/WishlistService.swift +++ b/Alfie/AlfieKit/Sources/Core/Services/Wishlist/WishlistService.swift @@ -1,9 +1,14 @@ +import Combine import Foundation import Models // TODO: Update with an actual implementation with storage public final class WishlistService: WishlistServiceProtocol { - private var products: [WishlistProduct] = [] + @Published private var products: [WishlistProduct] = [] + + public var productsPublisher: AnyPublisher<[WishlistProduct], Never> { + $products.eraseToAnyPublisher() + } public init() { } @@ -21,7 +26,7 @@ public final class WishlistService: WishlistServiceProtocol { products = products.filter { $0.product.id != wishlistProduct.product.id } } - public func getWishlistContent() -> [WishlistProduct] { - products + public func containsProduct(_ wishlistProduct: WishlistProduct) -> Bool { + products.contains { $0.id == wishlistProduct.id } } } diff --git a/Alfie/AlfieKit/Sources/Mocks/Core/Features/MockBagViewModel.swift b/Alfie/AlfieKit/Sources/Mocks/Core/Features/MockBagViewModel.swift index 34d3f1b5..83c8160a 100644 --- a/Alfie/AlfieKit/Sources/Mocks/Core/Features/MockBagViewModel.swift +++ b/Alfie/AlfieKit/Sources/Mocks/Core/Features/MockBagViewModel.swift @@ -7,11 +7,6 @@ public class MockBagViewModel: BagViewModelProtocol { self.products = products } - public var onViewDidAppearCalled: (() -> Void)? - public func viewDidAppear() { - onViewDidAppearCalled?() - } - public var onDidSelectDeleteCalled: ((BagProduct) -> Void)? public func didSelectDelete(for selectedProduct: BagProduct) { onDidSelectDeleteCalled?(selectedProduct) diff --git a/Alfie/AlfieKit/Sources/Mocks/Core/Features/MockProductDetailsViewModel.swift b/Alfie/AlfieKit/Sources/Mocks/Core/Features/MockProductDetailsViewModel.swift index 61f4c9e9..3f7c6956 100644 --- a/Alfie/AlfieKit/Sources/Mocks/Core/Features/MockProductDetailsViewModel.swift +++ b/Alfie/AlfieKit/Sources/Mocks/Core/Features/MockProductDetailsViewModel.swift @@ -19,6 +19,8 @@ public class MockProductDetailsViewModel: ProductDetailsViewModelProtocol { public var hasSingleImage: Bool = false public var priceType: PriceType? = nil public var isAddToBagEnabled: Bool = true + public var bagButtonTitle: String = "" + public var wishlistButtonTitle: String = "" public init(state: ViewState = .loading, productId: String = "", diff --git a/Alfie/AlfieKit/Sources/Mocks/Core/Services/MockBagService.swift b/Alfie/AlfieKit/Sources/Mocks/Core/Services/MockBagService.swift index ead71e2b..eb5e8cd4 100644 --- a/Alfie/AlfieKit/Sources/Mocks/Core/Services/MockBagService.swift +++ b/Alfie/AlfieKit/Sources/Mocks/Core/Services/MockBagService.swift @@ -1,8 +1,13 @@ +import Combine import Foundation import Models public final class MockBagService: BagServiceProtocol { - private var products: [BagProduct] = [] + @Published private var products: [BagProduct] = [] + + public var productsPublisher: AnyPublisher<[BagProduct], Never> { + $products.eraseToAnyPublisher() + } public init() { } @@ -16,7 +21,7 @@ public final class MockBagService: BagServiceProtocol { products = products.filter { $0.id != bagProduct.id } } - public func getBagContent() -> [BagProduct] { - products + public func containsProduct(_ bagProduct: BagProduct) -> Bool { + products.contains { $0.id == bagProduct.id } } } diff --git a/Alfie/AlfieKit/Sources/Mocks/Core/Services/MockWishlistService.swift b/Alfie/AlfieKit/Sources/Mocks/Core/Services/MockWishlistService.swift index e3e0ddfa..96c12b08 100644 --- a/Alfie/AlfieKit/Sources/Mocks/Core/Services/MockWishlistService.swift +++ b/Alfie/AlfieKit/Sources/Mocks/Core/Services/MockWishlistService.swift @@ -1,8 +1,13 @@ +import Combine import Foundation import Models public final class MockWishlistService: WishlistServiceProtocol { - private var products: [WishlistProduct] = [] + @Published private var products: [WishlistProduct] = [] + + public var productsPublisher: AnyPublisher<[WishlistProduct], Never> { + $products.eraseToAnyPublisher() + } public init() { } @@ -20,7 +25,7 @@ public final class MockWishlistService: WishlistServiceProtocol { products = products.filter { $0.product.id != wishlistProduct.product.id } } - public func getWishlistContent() -> [WishlistProduct] { - products + public func containsProduct(_ wishlistProduct: WishlistProduct) -> Bool { + products.contains { $0.id == wishlistProduct.id } } } diff --git a/Alfie/AlfieKit/Sources/Models/Features/BagViewModelProtocol.swift b/Alfie/AlfieKit/Sources/Models/Features/BagViewModelProtocol.swift index d26ec992..1a22663f 100644 --- a/Alfie/AlfieKit/Sources/Models/Features/BagViewModelProtocol.swift +++ b/Alfie/AlfieKit/Sources/Models/Features/BagViewModelProtocol.swift @@ -3,7 +3,6 @@ import Foundation public protocol BagViewModelProtocol: ObservableObject { var products: [BagProduct] { get } - func viewDidAppear() func didSelectDelete(for selectedProduct: BagProduct) func productCardViewModel(for selectedProduct: BagProduct) -> HorizontalProductCardViewModel } diff --git a/Alfie/AlfieKit/Sources/Models/Features/ProductDetailsViewModelProtocol.swift b/Alfie/AlfieKit/Sources/Models/Features/ProductDetailsViewModelProtocol.swift index 2ed3591e..5c22eb03 100644 --- a/Alfie/AlfieKit/Sources/Models/Features/ProductDetailsViewModelProtocol.swift +++ b/Alfie/AlfieKit/Sources/Models/Features/ProductDetailsViewModelProtocol.swift @@ -30,8 +30,8 @@ public enum ProductDetailsSection { case mediaCarousel case complementaryInfo case productDescription - case addToBag - case addToWishlist + case bagButton + case wishlistButton } // MARK: - ProductDetailsComplementaryInfoType @@ -69,6 +69,8 @@ public protocol ProductDetailsViewModelProtocol: ObservableObject { var hasSingleImage: Bool { get } var priceType: PriceType? { get } var isAddToBagEnabled: Bool { get } + var bagButtonTitle: String { get } + var wishlistButtonTitle: String { get } func viewDidAppear() func shouldShow(section: ProductDetailsSection) -> Bool diff --git a/Alfie/AlfieKit/Sources/Models/Features/WishlistViewModelProtocol.swift b/Alfie/AlfieKit/Sources/Models/Features/WishlistViewModelProtocol.swift index 09022e74..067bd7d2 100644 --- a/Alfie/AlfieKit/Sources/Models/Features/WishlistViewModelProtocol.swift +++ b/Alfie/AlfieKit/Sources/Models/Features/WishlistViewModelProtocol.swift @@ -3,7 +3,6 @@ import Foundation public protocol WishlistViewModelProtocol: ObservableObject { var products: [WishlistProduct] { get } - func viewDidAppear() func didSelectDelete(for wishlistProduct: WishlistProduct) func productCardViewModel(for wishlistProduct: WishlistProduct) -> VerticalProductCardViewModel } diff --git a/Alfie/AlfieKit/Sources/Models/Services/Bag/BagServiceProtocol.swift b/Alfie/AlfieKit/Sources/Models/Services/Bag/BagServiceProtocol.swift index 02c36deb..e9691c30 100644 --- a/Alfie/AlfieKit/Sources/Models/Services/Bag/BagServiceProtocol.swift +++ b/Alfie/AlfieKit/Sources/Models/Services/Bag/BagServiceProtocol.swift @@ -1,7 +1,10 @@ +import Combine import Foundation public protocol BagServiceProtocol { + var productsPublisher: AnyPublisher<[BagProduct], Never> { get } + func addProduct(_ bagProduct: BagProduct) func removeProduct(_ bagProduct: BagProduct) - func getBagContent() -> [BagProduct] + func containsProduct(_ bagProduct: BagProduct) -> Bool } diff --git a/Alfie/AlfieKit/Sources/Models/Services/Wishlist/WishlistServiceProtocol.swift b/Alfie/AlfieKit/Sources/Models/Services/Wishlist/WishlistServiceProtocol.swift index c6caae77..c1f499d2 100644 --- a/Alfie/AlfieKit/Sources/Models/Services/Wishlist/WishlistServiceProtocol.swift +++ b/Alfie/AlfieKit/Sources/Models/Services/Wishlist/WishlistServiceProtocol.swift @@ -1,8 +1,11 @@ +import Combine import Foundation public protocol WishlistServiceProtocol { + var productsPublisher: AnyPublisher<[WishlistProduct], Never> { get } + func addProduct(_ wishlistProduct: WishlistProduct) func removeProduct(_ wishlistProduct: WishlistProduct) func removeProductVariants(_ wishlistProduct: WishlistProduct) - func getWishlistContent() -> [WishlistProduct] + func containsProduct(_ wishlistProduct: WishlistProduct) -> Bool } diff --git a/Alfie/AlfieKit/Sources/SharedUI/Localization/L10n+Generated.swift b/Alfie/AlfieKit/Sources/SharedUI/Localization/L10n+Generated.swift index 1b913869..0308d730 100644 --- a/Alfie/AlfieKit/Sources/SharedUI/Localization/L10n+Generated.swift +++ b/Alfie/AlfieKit/Sources/SharedUI/Localization/L10n+Generated.swift @@ -194,6 +194,18 @@ public enum L10n { public static let cta = L10n.tr("L10n", "product.out_of_stock.button.cta") } } + public enum RemoveFromBag { + public enum Button { + /// Remove from bag + public static let cta = L10n.tr("L10n", "product.remove_from_bag.button.cta") + } + } + public enum RemoveFromWishlist { + public enum Button { + /// Remove from wishlist + public static let cta = L10n.tr("L10n", "product.remove_from_wishlist.button.cta") + } + } public enum Size { /// Size public static let title = L10n.tr("L10n", "product.size.title") @@ -470,6 +482,8 @@ public extension L10n { case productColorTitle = "product.color.title" case productOneSizeTitle = "product.one_size.title" case productOutOfStockButtonCta = "product.out_of_stock.button.cta" + case productRemoveFromBagButtonCta = "product.remove_from_bag.button.cta" + case productRemoveFromWishlistButtonCta = "product.remove_from_wishlist.button.cta" case productSizeTitle = "product.size.title" case productSizeNoSelectionTitle = "product.size.no_selection.title" case searchScreenEmptyViewMessage = "search.screen.empty_view.message" diff --git a/Alfie/AlfieKit/Sources/SharedUI/Resources/Localization/L10n.xcstrings b/Alfie/AlfieKit/Sources/SharedUI/Resources/Localization/L10n.xcstrings index 95c83cd2..c5f13d06 100644 --- a/Alfie/AlfieKit/Sources/SharedUI/Resources/Localization/L10n.xcstrings +++ b/Alfie/AlfieKit/Sources/SharedUI/Resources/Localization/L10n.xcstrings @@ -420,6 +420,28 @@ } } }, + "product.remove_from_bag.button.cta" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remove from bag" + } + } + } + }, + "product.remove_from_wishlist.button.cta" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remove from wishlist" + } + } + } + }, "product.size.no_selection.title" : { "extractionState" : "manual", "localizations" : { diff --git a/Alfie/AlfieTests/Features/ProductDetailsViewModelTests.swift b/Alfie/AlfieTests/Features/ProductDetailsViewModelTests.swift index 6d040963..530bd5b5 100644 --- a/Alfie/AlfieTests/Features/ProductDetailsViewModelTests.swift +++ b/Alfie/AlfieTests/Features/ProductDetailsViewModelTests.swift @@ -732,7 +732,7 @@ final class ProductDetailsViewModelTests: XCTestCase { XCTAssertEmitsValue(from: sut.$state.drop(while: \.isLoading), afterTrigger: { self.sut.viewDidAppear() }) - XCTAssertTrue(sut.shouldShow(section: .addToBag)) + XCTAssertTrue(sut.shouldShow(section: .bagButton)) } func test_reports_add_to_bag_section_as_not_visible_if_state_is_failure() { @@ -744,13 +744,13 @@ final class ProductDetailsViewModelTests: XCTestCase { XCTAssertEmitsValue(from: sut.$state.drop(while: \.isLoading), afterTrigger: { self.sut.viewDidAppear() }) - XCTAssertFalse(sut.shouldShow(section: .addToBag)) + XCTAssertFalse(sut.shouldShow(section: .bagButton)) } func test_reports_add_to_bag_section_as_not_visible_if_state_is_loading() { initViewModel() - XCTAssertFalse(sut.shouldShow(section: .addToBag)) + XCTAssertFalse(sut.shouldShow(section: .bagButton)) } // MARK: - Share diff --git a/Alfie/Checksums/swiftgen_checksum.txt b/Alfie/Checksums/swiftgen_checksum.txt index 8105d7df..d048a25b 100644 --- a/Alfie/Checksums/swiftgen_checksum.txt +++ b/Alfie/Checksums/swiftgen_checksum.txt @@ -1 +1 @@ -b00b49f7ac3847173e83b27812fb98832a22f311 +6b80c5a3a381aa06d4202b4402a0340185cb7bba