Skip to content

Commit 44487aa

Browse files
#41: добавлен FavoritesManager (#49)
1 parent 7d2f958 commit 44487aa

File tree

11 files changed

+175
-44
lines changed

11 files changed

+175
-44
lines changed

EatHub/EatHub/Application/AppDependencies.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,6 @@ struct AppDependencies {
3939
}
4040

4141
func makeFavoriteViewModel() -> FavoriteViewModel {
42-
FavoriteViewModel()
42+
FavoriteViewModel(favoritesManager: FavoritesManager(), mealsService: mealsService)
4343
}
4444
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// UserDefaults+KeyValueStore.swift
3+
// EatHub
4+
//
5+
// Created by Stepan Chuiko on 30.03.2025.
6+
//
7+
import Foundation
8+
9+
extension UserDefaults: KeyValueStore {
10+
func array<T>(forKey defaultName: String) -> [T] {
11+
(self.array(forKey: defaultName) as? [T]) ?? []
12+
}
13+
}

EatHub/EatHub/Mappers/MealMapper/MealMapper.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,9 @@ extension MealItemResponseModel {
3838
)
3939
}
4040
}
41+
42+
extension Meal {
43+
func mapToRecipe() -> RecipeViewModel {
44+
RecipeViewModel(id: id, name: name, imageName: thumbnail ?? "MealTemplate")
45+
}
46+
}

EatHub/EatHub/Modules/Favorite/FavoriteView.swift

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ struct FavoriteView: View {
1313
}
1414
.background(Color(.systemGroupedBackground))
1515
.navigationBarHidden(true)
16+
.onAppear {
17+
viewModel.refreshFavorites()
18+
}
1619
}
1720
}
1821
}
@@ -27,25 +30,20 @@ private extension FavoriteView {
2730

2831
var favoritesList: some View {
2932
LazyVStack(spacing: 8) {
30-
ForEach(viewModel.recipes) { recipe in
31-
// TODO: Добавить переход
32-
// NavigationLink(destination: RecipeDetailView(recipe: recipe)) {
33+
ForEach(viewModel.likedRecipes) { recipe in
34+
// TODO: Добавить переход
35+
// NavigationLink(destination: RecipeDetailView(recipe: recipe)) {
3336
RecipeRow(
3437
recipe: recipe,
3538
onToggleFavorite: {
3639
viewModel.toggleFavorite(for: recipe)
3740
}
3841
)
39-
// }
42+
// }
4043
.buttonStyle(PlainButtonStyle())
4144
}
4245
}
4346
.padding(.horizontal)
4447
.padding(.top, 8)
4548
}
4649
}
47-
48-
#Preview {
49-
let viewModel = FavoriteViewModel()
50-
FavoriteView(viewModel: viewModel)
51-
}
Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,59 @@
11
import Foundation
2+
import Combine
3+
4+
final class FavoriteViewModel: ObservableObject {
5+
@Published var likedRecipes: [RecipeViewModel] = []
6+
var recipesIdentifiers: [String] = []
27

3-
class FavoriteViewModel: ObservableObject {
4-
@Published var recipes: [Recipe] = []
58
let title = "Favourites"
69

7-
init() {
8-
loadMockData()
10+
private let favoritesManager: FavoritesManagerInterface
11+
private let mealsService: MealsServiceInterface
12+
private var cancellables = Set<AnyCancellable>()
13+
14+
init(favoritesManager: FavoritesManagerInterface, mealsService: MealsServiceInterface) {
15+
self.favoritesManager = favoritesManager
16+
self.mealsService = mealsService
17+
18+
// убрать
19+
favoritesManager.populateInitialFavorites(with: ["52943", "52869", "52883", "52823"])
920
}
1021

11-
func toggleFavorite(for recipe: Recipe) {
12-
guard let index = recipes.firstIndex(of: recipe) else { return }
13-
recipes[index].isFavorite.toggle()
22+
func toggleFavorite(for recipe: RecipeViewModel) {
23+
if recipe.isFavorite {
24+
favoritesManager.remove(recipeID: recipe.id)
25+
} else {
26+
favoritesManager.add(recipeID: recipe.id)
27+
}
28+
29+
recipe.isFavorite.toggle()
1430
}
1531

16-
private func loadMockData() {
17-
recipes = [
18-
Recipe(id: 0, name: "Паста Карбонара", imageName: "caesar"),
19-
Recipe(id: 1, name: "Пицца Маргарита", imageName: "caesar"),
20-
Recipe(id: 2, name: "Салат Цезарь", imageName: "caesar"),
21-
Recipe(id: 3, name: "Паста Карбонара", imageName: "caesar"),
22-
Recipe(id: 4, name: "Пицца Маргарита", imageName: "caesar"),
23-
Recipe(id: 5, name: "Салат Цезарь", imageName: "caesar"),
24-
Recipe(id: 6, name: "Паста Карбонара", imageName: "caesar"),
25-
Recipe(id: 7, name: "Пицца Маргарита", imageName: "caesar"),
26-
Recipe(id: 8, name: "Салат Цезарь", imageName: "caesar"),
27-
Recipe(id: 9, name: "Паста Карбонара", imageName: "caesar"),
28-
Recipe(id: 10, name: "Пицца Маргарита", imageName: "caesar"),
29-
Recipe(id: 11, name: "Салат Цезарь", imageName: "caesar")
30-
]
32+
func refreshFavorites() {
33+
cancellables.removeAll()
34+
35+
let currentFavoriteIDs = Set(favoritesManager.allFavorites())
36+
37+
likedRecipes.removeAll { !currentFavoriteIDs.contains($0.id) }
38+
39+
let existingIDs = Set(likedRecipes.map { $0.id })
40+
let newIDs = currentFavoriteIDs.subtracting(existingIDs)
41+
42+
loadNewRecipes(ids: Array(newIDs))
43+
}
44+
45+
private func loadNewRecipes(ids: [String]) {
46+
cancellables.removeAll()
47+
48+
for id in ids {
49+
mealsService.fetchMeal(id: id)
50+
.receive(on: DispatchQueue.main)
51+
.sink { _ in } receiveValue: { [weak self] meal in
52+
if let recipe = meal?.mapToRecipe() {
53+
self?.likedRecipes.append(recipe)
54+
}
55+
}
56+
.store(in: &cancellables)
57+
}
3158
}
3259
}

EatHub/EatHub/Modules/Favorite/Models/Recipe.swift

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Foundation
2+
3+
class RecipeViewModel: ObservableObject, Identifiable {
4+
let id: String
5+
@Published var name: String
6+
@Published var imageName: String
7+
@Published var isFavorite: Bool = true
8+
9+
init(id: String, name: String, imageName: String) {
10+
self.id = id
11+
self.name = name
12+
self.imageName = imageName
13+
}
14+
}

EatHub/EatHub/Modules/Favorite/RecipeRow.swift

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

33
struct RecipeRow: View {
4-
let recipe: Recipe
4+
@ObservedObject var recipe: RecipeViewModel
55
let onToggleFavorite: () -> Void
66

77
private enum Constants {
@@ -14,12 +14,19 @@ struct RecipeRow: View {
1414

1515
var body: some View {
1616
HStack {
17-
Image(recipe.imageName)
18-
.resizable()
19-
.aspectRatio(contentMode: .fill)
17+
if let url = URL(string: recipe.imageName) {
18+
CachedAsyncImage(url: url) { image in
19+
image
20+
.resizable()
21+
.aspectRatio(contentMode: .fill)
22+
} placeholder: {
23+
Color.gray.opacity(0.3)
24+
.skeletonable(true)
25+
}
2026
.frame(width: Constants.imageWidth, height: Constants.imageHeight)
2127
.clipped()
2228
.cornerRadius(Constants.imageCornerRadius)
29+
}
2330

2431
Text(recipe.name)
2532
.font(.headline)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// FavoritesManager.swift
3+
// EatHub
4+
//
5+
// Created by Stepan Chuiko on 29.03.2025.
6+
//
7+
import Foundation
8+
9+
final class FavoritesManager {
10+
private let store: KeyValueStore
11+
private let favoritesKey = "favorite_recipes_ids"
12+
13+
init(store: KeyValueStore = UserDefaults.standard) {
14+
self.store = store
15+
}
16+
}
17+
18+
extension FavoritesManager: FavoritesManagerInterface {
19+
func add(recipeID: String) {
20+
var current = allFavorites()
21+
guard !current.contains(recipeID) else { return }
22+
current.append(recipeID)
23+
save(current)
24+
}
25+
26+
func remove(recipeID: String) {
27+
var current = allFavorites()
28+
current.removeAll { $0 == recipeID }
29+
save(current)
30+
}
31+
32+
func isFavorite(recipeID: String) -> Bool {
33+
allFavorites().contains(recipeID)
34+
}
35+
36+
func allFavorites() -> [String] {
37+
store.array(forKey: favoritesKey)
38+
}
39+
40+
func populateInitialFavorites(with ids: [String]) {
41+
var current = Set(allFavorites())
42+
ids.forEach { current.insert($0) }
43+
save(Array(current))
44+
}
45+
46+
private func save(_ ids: [String]) {
47+
store.set(ids, forKey: favoritesKey)
48+
}
49+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// FavoritesManagerInterface.swift
3+
// EatHub
4+
//
5+
// Created by Stepan Chuiko on 29.03.2025.
6+
//
7+
8+
protocol FavoritesManagerInterface {
9+
func add(recipeID: String)
10+
func remove(recipeID: String)
11+
func isFavorite(recipeID: String) -> Bool
12+
func allFavorites() -> [String]
13+
func populateInitialFavorites(with ids: [String])
14+
}

0 commit comments

Comments
 (0)