Skip to content

Commit 5522d64

Browse files
ALFMOB-149: Bag & Wishlist - Redirect to PDP
In the **Bag** and **Wishlist** screens, tapping on a product cell now redirects the user to the **PDP** of the selected item, ensuring a smooth transition while preserving selected attributes like size and color. **Changes & Improvements:** - Made product cells tappable, including a tap effect for better user feedback. - Renamed `SelectionProduct` to `SelectedProduct` for better clarity. - Updated `SelectedProduct` to include `Product` and `Product.Variant` properties. - Implemented a mechanism to open **PDP** with a `SelectedProduct`. - Fixed an issue in `buildColorAndSizingSelectionConfigurations` where the `Product.Variant` parameter was not being properly used.
1 parent 539ca13 commit 5522d64

24 files changed

Lines changed: 208 additions & 122 deletions

Alfie/Alfie/Extensions/SelectionProduct+Extension.swift renamed to Alfie/Alfie/Extensions/SelectedProduct+Extension.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Foundation
22
import Models
33

4-
extension SelectionProduct {
4+
extension SelectedProduct {
55
var sizeText: String {
66
var sizeValue: String = ""
77
if let size {

Alfie/Alfie/Navigation/Coordinator.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ final class Coordinator: ObservableObject, CoordinatorProtocol {
143143
navigationAdapter.push(.productDetails(.product(product)))
144144
}
145145

146+
public func openDetails(for selectedProduct: SelectedProduct) {
147+
navigationAdapter.push(.productDetails(.selectedProduct(selectedProduct)))
148+
}
149+
146150
// MARK: - Brands
147151

148152
public func openBrands() {

Alfie/Alfie/Navigation/Screen.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ enum HomeTabConfig: Equatable, Hashable {
4343
enum ThemedProductDetailsScreen: Equatable, Hashable {
4444
case id(_ id: String)
4545
case product(_ product: Product)
46+
case selectedProduct(_ selectedProduct: SelectedProduct)
4647
}
4748

4849
struct ProductListingScreenConfiguration: Equatable, Hashable {

Alfie/Alfie/Navigation/TabCoordinator.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ final class TabCoordinator: TabCoordinatorProtocol, ObservableObject {
112112

113113
case .product(let product):
114114
coordinator.openDetails(for: product)
115+
116+
case .selectedProduct(let selectedProduct):
117+
coordinator.openDetails(for: selectedProduct)
115118
}
116119

117120
case .categoryList(let categories, let title):

Alfie/Alfie/Navigation/ViewFactory.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,23 @@ final class ViewFactory: ViewFactoryProtocol {
157157
)
158158
)
159159
)
160+
161+
case .selectedProduct(let selectedProduct):
162+
ProductDetailsView(
163+
viewModel: ProductDetailsViewModel(
164+
productId: selectedProduct.product.id,
165+
product: selectedProduct.product,
166+
selectedProduct: selectedProduct,
167+
dependencies: ProductDetailsDependencyContainer(
168+
productService: serviceProvider.productService,
169+
webUrlProvider: serviceProvider.webUrlProvider,
170+
bagService: serviceProvider.bagService,
171+
wishlistService: serviceProvider.wishlistService,
172+
configurationService: serviceProvider.configurationService,
173+
analytics: serviceProvider.analytics
174+
)
175+
)
176+
)
160177
}
161178

162179
case .recentSearches:

Alfie/Alfie/Views/BagView/BagView.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Mocks
66
#endif
77

88
struct BagView<ViewModel: BagViewModelProtocol>: View {
9+
@EnvironmentObject var coordinador: Coordinator
910
@StateObject private var viewModel: ViewModel
1011

1112
init(viewModel: ViewModel) {
@@ -15,7 +16,14 @@ struct BagView<ViewModel: BagViewModelProtocol>: View {
1516
var body: some View {
1617
List {
1718
ForEach(viewModel.products) { product in
18-
HorizontalProductCard(viewModel: viewModel.productCardViewModel(for: product))
19+
Button(
20+
action: { coordinador.openDetails(for: product) },
21+
label: {
22+
HorizontalProductCard(viewModel: viewModel.productCardViewModel(for: product))
23+
.contentShape(Rectangle())
24+
}
25+
)
26+
.buttonStyle(.plain)
1927
.listRowInsets(EdgeInsets())
2028
}
2129
.onDelete { offsets in

Alfie/Alfie/Views/BagView/BagViewModel.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22
import Models
33

44
final class BagViewModel: BagViewModelProtocol {
5-
@Published private(set) var products: [SelectionProduct]
5+
@Published private(set) var products: [SelectedProduct]
66

77
private let dependencies: BagDependencyContainer
88

@@ -17,22 +17,22 @@ final class BagViewModel: BagViewModelProtocol {
1717
products = dependencies.bagService.getBagContent()
1818
}
1919

20-
func didSelectDelete(for product: SelectionProduct) {
21-
dependencies.bagService.removeProduct(product)
20+
func didSelectDelete(for selectedProduct: SelectedProduct) {
21+
dependencies.bagService.removeProduct(selectedProduct)
2222
products = dependencies.bagService.getBagContent()
23-
dependencies.analytics.trackRemoveFromBag(productID: product.id)
23+
dependencies.analytics.trackRemoveFromBag(productID: selectedProduct.product.id)
2424
}
2525

26-
func productCardViewModel(for product: SelectionProduct) -> HorizontalProductCardViewModel {
26+
func productCardViewModel(for selectedProduct: SelectedProduct) -> HorizontalProductCardViewModel {
2727
.init(
28-
image: product.media.first?.asImage?.url,
29-
designer: product.brand.name,
30-
name: product.name,
28+
image: selectedProduct.media.first?.asImage?.url,
29+
designer: selectedProduct.brand.name,
30+
name: selectedProduct.name,
3131
colorTitle: L10n.Product.Color.title + ":",
32-
color: product.colour?.name ?? "",
32+
color: selectedProduct.colour?.name ?? "",
3333
sizeTitle: L10n.Product.Size.title + ":",
34-
size: product.size == nil ? L10n.Product.OneSize.title : product.sizeText,
35-
priceType: product.priceType
34+
size: selectedProduct.size == nil ? L10n.Product.OneSize.title : selectedProduct.sizeText,
35+
priceType: selectedProduct.priceType
3636
)
3737
}
3838
}

Alfie/Alfie/Views/ProductDetails/ProductDetailsViewModel.swift

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ final class ProductDetailsViewModel: ProductDetailsViewModelProtocol {
1515
private(set) var colorSelectionConfiguration: ColorAndSizingSelectorConfiguration<ColorSwatch> = .init(items: [])
1616
private(set) var sizingSelectionConfiguration: ColorAndSizingSelectorConfiguration<SizingSwatch> = .init(items: [])
1717
public let productId: String
18+
private let initialSelectedProduct: SelectedProduct?
1819

1920
private var product: Product? {
2021
guard case .success(let model) = state else {
@@ -26,7 +27,7 @@ final class ProductDetailsViewModel: ProductDetailsViewModelProtocol {
2627

2728
private var selectedVariant: Product.Variant? {
2829
guard case .success(let model) = state else {
29-
return baseProduct?.defaultVariant
30+
return initialSelectedProduct?.selectedVariant ?? baseProduct?.defaultVariant
3031
}
3132

3233
return model.selectedVariant
@@ -87,16 +88,32 @@ final class ProductDetailsViewModel: ProductDetailsViewModelProtocol {
8788
product?.priceType
8889
}
8990

90-
init(productId: String, product: Product?, dependencies: ProductDetailsDependencyContainer) {
91+
init(
92+
productId: String,
93+
product: Product?,
94+
selectedProduct: SelectedProduct? = nil,
95+
dependencies: ProductDetailsDependencyContainer
96+
) {
9197
self.productId = productId
98+
self.initialSelectedProduct = selectedProduct
9299
baseProduct = product
93100
self.dependencies = dependencies
94101

95-
if let baseProduct {
102+
switch (product, selectedProduct) {
103+
case (.some(let product), .none):
96104
buildColorAndSizingSelectionConfigurations(
97-
product: baseProduct,
98-
selectedVariant: baseProduct.defaultVariant
105+
product: product,
106+
selectedVariant: product.defaultVariant
99107
)
108+
109+
case (_, .some(let selectedProduct)):
110+
buildColorAndSizingSelectionConfigurations(
111+
product: selectedProduct.product,
112+
selectedVariant: selectedProduct.selectedVariant
113+
)
114+
115+
default:
116+
break
100117
}
101118
}
102119

@@ -204,13 +221,14 @@ final class ProductDetailsViewModel: ProductDetailsViewModelProtocol {
204221
return
205222
}
206223

207-
buildColorAndSizingSelectionConfigurations(product: product, selectedVariant: product.defaultVariant)
208-
state = .success(.init(product: product, selectedVariant: product.defaultVariant))
224+
let selectedVariant = initialSelectedProduct?.selectedVariant ?? product.defaultVariant
225+
buildColorAndSizingSelectionConfigurations(product: product, selectedVariant: selectedVariant)
226+
state = .success(.init(product: product, selectedVariant: selectedVariant))
209227
}
210228

211-
private func buildColorAndSizingSelectionConfigurations(product: Product, selectedVariant: Product.Variant?) {
212-
buildColorSelectionConfiguration(product: product, selectedVariant: product.defaultVariant)
213-
buildSizingSelectionConfiguration(product: product, selectedVariant: product.defaultVariant)
229+
private func buildColorAndSizingSelectionConfigurations(product: Product, selectedVariant: Product.Variant) {
230+
buildColorSelectionConfiguration(product: product, selectedVariant: selectedVariant)
231+
buildSizingSelectionConfiguration(product: product, selectedVariant: selectedVariant)
214232
}
215233

216234
private func buildColorSelectionConfiguration(product: Product, selectedVariant: Product.Variant?) {
@@ -363,14 +381,14 @@ final class ProductDetailsViewModel: ProductDetailsViewModelProtocol {
363381
state = .success(.init(product: product, selectedVariant: variant))
364382
}
365383

366-
private var selectedProduct: SelectionProduct? {
384+
private var selectedProduct: SelectedProduct? {
367385
guard
368386
let product,
369387
let selectedVariant
370388
else {
371-
return nil
389+
return initialSelectedProduct
372390
}
373391

374-
return SelectionProduct(product: product, selectedVariant: selectedVariant)
392+
return SelectedProduct(product: product, selectedVariant: selectedVariant)
375393
}
376394
}

Alfie/Alfie/Views/ProductListing/ProductListingViewModel.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ final class ProductListingViewModel: ProductListingViewModelProtocol {
1414
@Published var style: ProductListingListStyle
1515
@Published var showRefine = false
1616
@Published var sortOption: String?
17-
@Published private(set) var wishlistContent: [SelectionProduct]
17+
@Published private(set) var wishlistContent: [SelectedProduct]
1818
@Published private(set) var state: PaginatedViewState<ProductListingViewStateModel, ProductListingViewErrorType>
1919

2020
private enum Constants {
@@ -79,16 +79,17 @@ final class ProductListingViewModel: ProductListingViewModelProtocol {
7979
func didSelect(_: Product) {}
8080

8181
func isFavoriteState(for product: Product) -> Bool {
82-
wishlistContent.contains { $0.id == product.defaultVariant.sku }
82+
wishlistContent.contains { $0.product.defaultVariant.sku == product.defaultVariant.sku }
8383
}
8484

8585
func didTapAddToWishlist(for product: Product, isFavorite: Bool) {
8686
if !isFavorite {
87-
let selectedProduct = SelectionProduct(product: product)
87+
let selectedProduct = SelectedProduct(product: product)
8888
dependencies.wishlistService.addProduct(selectedProduct)
89-
dependencies.analytics.trackAddToWishlist(productID: selectedProduct.id)
89+
dependencies.analytics.trackAddToWishlist(productID: product.id)
9090
} else {
91-
dependencies.wishlistService.removeProduct(product.defaultVariant.sku)
91+
let selectedProduct = SelectedProduct(product: product)
92+
dependencies.wishlistService.removeProduct(selectedProduct)
9293
dependencies.analytics.trackRemoveFromWishlist(productID: product.id)
9394
}
9495
wishlistContent = dependencies.wishlistService.getWishlistContent()

Alfie/Alfie/Views/WishlistView/WishlistView.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Mocks
66
#endif
77

88
struct WishlistView<ViewModel: WishlistViewModelProtocol>: View {
9+
@EnvironmentObject var coordinador: Coordinator
910
@StateObject private var viewModel: ViewModel
1011

1112
init(viewModel: ViewModel) {
@@ -22,11 +23,18 @@ struct WishlistView<ViewModel: WishlistViewModelProtocol>: View {
2223
spacing: Spacing.space200
2324
) {
2425
ForEach(viewModel.products) { product in
25-
VerticalProductCard(
26-
viewModel: viewModel.productCardViewModel(for: product)
27-
) { _, type in
28-
handleUserAction(forProduct: product, actionType: type)
29-
}
26+
Button(
27+
action: { coordinador.openDetails(for: product) },
28+
label: {
29+
VerticalProductCard(
30+
viewModel: viewModel.productCardViewModel(for: product)
31+
) { _, type in
32+
handleUserAction(forProduct: product, actionType: type)
33+
}
34+
}
35+
)
36+
.buttonStyle(.plain)
37+
.listRowInsets(EdgeInsets())
3038
}
3139
}
3240
.padding(.horizontal, Spacing.space200)
@@ -42,7 +50,7 @@ struct WishlistView<ViewModel: WishlistViewModelProtocol>: View {
4250
// MARK: - Private Methods
4351

4452
private extension WishlistView {
45-
func handleUserAction(forProduct product: SelectionProduct, actionType: VerticalProductCard.ProductUserActionType) {
53+
func handleUserAction(forProduct product: SelectedProduct, actionType: VerticalProductCard.ProductUserActionType) {
4654
// swiftlint:disable vertical_whitespace_between_cases
4755
switch actionType {
4856
case .remove:

0 commit comments

Comments
 (0)