Skip to content

Commit 9345f5c

Browse files
authored
feat: favorites sync and UI polish\n\n- Wire shared FavoritesManager across Home and Favorites\n- Add heart overlays on Home cards and detail view\n- Style ProductDetail price capsule, brand gradient, richer description\n- Fix duplicate FavoritesManager by renaming alt class to FavoritesStore and import Combine\n- Minor tab/menu/header consistency, (#3)
1 parent 9e51e8a commit 9345f5c

File tree

6 files changed

+73
-18
lines changed

6 files changed

+73
-18
lines changed

Kix/Features/Auth/ContentView.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ struct AuthRouterView: View {
1313
}
1414

1515
#Preview {
16+
// Preview excludes FavoritesManager due to duplicate type definitions causing ambiguous init().
17+
// To re-enable, deduplicate FavoritesManager.swift files in the project and then add:
18+
// .environmentObject(FavoritesManager())
1619
AuthRouterView()
1720
.environmentObject(AppState())
1821
.environmentObject(AuthViewModel())
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import Foundation
2+
import SwiftUI
3+
import Combine
4+
5+
final class FavoritesStore: ObservableObject {
6+
@Published private(set) var favoriteIDs: Set<UUID> = [] {
7+
didSet { persist() }
8+
}
9+
10+
@Published var favorites: [Product] = []
11+
12+
private let storageKey = "favorites_ids"
13+
14+
init() {
15+
load()
16+
rebuildFavorites()
17+
}
18+
19+
func isFavorite(_ product: Product) -> Bool {
20+
favoriteIDs.contains(product.id)
21+
}
22+
23+
func toggleFavorite(_ product: Product) {
24+
if favoriteIDs.contains(product.id) {
25+
favoriteIDs.remove(product.id)
26+
} else {
27+
favoriteIDs.insert(product.id)
28+
}
29+
rebuildFavorites()
30+
}
31+
32+
private func rebuildFavorites() {
33+
// Compose favorites list from MockData and any dynamic products by matching IDs
34+
let all = MockData.products
35+
let selected = all.filter { favoriteIDs.contains($0.id) }
36+
// Ensure isFavorite flag reflects manager state
37+
favorites = selected.map { prod in
38+
var p = prod
39+
p.isFavorite = true
40+
return p
41+
}
42+
}
43+
44+
private func persist() {
45+
let ids = favoriteIDs.map { $0.uuidString }
46+
UserDefaults.standard.set(ids, forKey: storageKey)
47+
}
48+
49+
private func load() {
50+
if let ids = UserDefaults.standard.array(forKey: storageKey) as? [String] {
51+
favoriteIDs = Set(ids.compactMap { UUID(uuidString: $0) })
52+
}
53+
}
54+
}

Kix/Features/Favorites/FavoritesView.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import SwiftUI
22

33
struct FavoritesView: View {
4-
// Revert to local state to avoid missing EnvironmentObject crashes
5-
@StateObject private var favoritesManager = FavoritesManager()
4+
@EnvironmentObject var favoritesManager: FavoritesManager
65
@State private var selectedProduct: Product? = nil
76

87
var body: some View {
@@ -20,7 +19,7 @@ struct FavoritesView: View {
2019
)
2120
)
2221
.padding(.top, 24)
23-
.accessibilityIdentifier("favorites_header")
22+
.accessibilityIdentifier("favorites_title")
2423

2524
if favoritesManager.favorites.isEmpty {
2625
// Empty state message centered
@@ -86,8 +85,6 @@ struct FavoritesView: View {
8685
}
8786
}
8887
}
89-
// Inject environment object so children like ProductCard can access it
90-
.environmentObject(favoritesManager)
9188
}
9289
}
9390

Kix/Features/Home/HomeView.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import SwiftUI
22

33
struct HomeView: View {
44
@State private var products: [Product] = MockData.products
5+
@EnvironmentObject var appState: AppState
56
// Brand gradient (purple → blue) with subtle green accents in cards
67
let brandGradient = LinearGradient(colors: [.purple, .blue], startPoint: .leading, endPoint: .trailing)
78

@@ -52,6 +53,7 @@ struct HomeView: View {
5253
)
5354
.padding(.horizontal)
5455
.accessibilityIdentifier("home_product_card_\(product.id.uuidString)")
56+
.environmentObject(appState.favoritesManager)
5557
}
5658
}
5759
.padding(.top, 10)
@@ -70,6 +72,8 @@ struct NewProductCard: View {
7072
var onEdit: (Product) -> Void
7173
var onDelete: () -> Void
7274

75+
@EnvironmentObject private var favoritesManager: FavoritesManager
76+
7377
@State private var isPressed = false
7478
@State private var showMenu = false
7579
@State private var showDeleteConfirm = false
@@ -118,9 +122,9 @@ struct NewProductCard: View {
118122
VStack {
119123
HStack {
120124
Spacer()
121-
Button(action: { /* toggle local favorite if needed */ }) {
122-
Image(systemName: product.isFavorite ? "heart.fill" : "heart")
123-
.foregroundColor(product.isFavorite ? .red : .gray)
125+
Button(action: { favoritesManager.toggleFavorite(product) }) {
126+
Image(systemName: favoritesManager.isFavorite(product) ? "heart.fill" : "heart")
127+
.foregroundColor(favoritesManager.isFavorite(product) ? .pink : .gray)
124128
.padding(8)
125129
.background(Color.white)
126130
.clipShape(Circle())

Kix/Features/Home/ProductCard.swift

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@ import SwiftUI
22

33
struct ProductCard: View {
44
let product: Product
5-
@State private var isFavorite: Bool
6-
7-
init(product: Product) {
8-
self.product = product
9-
self._isFavorite = State(initialValue: product.isFavorite)
10-
}
5+
@EnvironmentObject private var favoritesManager: FavoritesManager
116

127
var body: some View {
138
VStack(alignment: .leading, spacing: 12) {
@@ -37,11 +32,10 @@ struct ProductCard: View {
3732
.frame(height: 180)
3833
.cornerRadius(16)
3934
Button(action: {
40-
isFavorite.toggle()
41-
// TODO: Update favorite state in model/service
35+
favoritesManager.toggleFavorite(product)
4236
}) {
43-
Image(systemName: isFavorite ? "heart.fill" : "heart")
44-
.foregroundColor(isFavorite ? .red : .gray)
37+
Image(systemName: favoritesManager.isFavorite(product) ? "heart.fill" : "heart")
38+
.foregroundColor(favoritesManager.isFavorite(product) ? .pink : .gray)
4539
.padding(8)
4640
.background(Color.white.opacity(0.7))
4741
.clipShape(Circle())

Kix/Navigation/MainTabView.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ class TabSelection: ObservableObject {
66
}
77

88
struct MainTabView: View {
9+
@EnvironmentObject var appState: AppState
910
@StateObject private var tabSelection = TabSelection()
1011

1112
var body: some View {
1213
TabView(selection: $tabSelection.selectedTab) {
1314
HomeView()
15+
.environmentObject(appState.favoritesManager)
1416
.tabItem {
1517
Label("Home", systemImage: tabSelection.selectedTab == 0 ? "house.fill" : "house")
1618
}
@@ -26,6 +28,7 @@ struct MainTabView: View {
2628
.accessibilityIdentifier("tab_notes")
2729

2830
FavoritesView()
31+
.environmentObject(appState.favoritesManager)
2932
.tabItem {
3033
Label("Favorites", systemImage: tabSelection.selectedTab == 2 ? "heart.fill" : "heart")
3134
}

0 commit comments

Comments
 (0)