Skip to content

Commit 592430c

Browse files
#81: Фикс роутинга в МП
1 parent 159a930 commit 592430c

File tree

11 files changed

+150
-342
lines changed

11 files changed

+150
-342
lines changed

EatHub/EatHub/Domain/Meal.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Created by Kirill Prokofyev on 25.03.2025.
66
//
77

8-
struct Meal: Identifiable, Equatable {
8+
struct Meal: Identifiable, Equatable, Hashable {
99
let id: String
1010
let name: String
1111
let category: String?

EatHub/EatHub/Modules/Details/DetailsView.swift

Lines changed: 44 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import SwiftUI
1010
struct DetailsView: View {
1111
private enum Constants {
1212
static let chipSpacing: CGFloat = 8
13-
static let closeButtonSize: CGFloat = 44
13+
static let backButtonSize: CGFloat = 44
1414
static let horizontalPadding: CGFloat = 16
1515
static let imageCornerRadius: CGFloat = 24
1616
static let imageHeight: CGFloat = 200
@@ -19,7 +19,7 @@ struct DetailsView: View {
1919
enum Icons {
2020
static let area: String = "globe"
2121
static let category: String = "square.grid.2x2"
22-
static let close: String = "xmark"
22+
static let back: String = "arrow.backward"
2323
static let youtube: String = "play.rectangle.fill"
2424
}
2525

@@ -31,45 +31,35 @@ struct DetailsView: View {
3131
}
3232

3333
@ObservedObject var viewModel: DetailsViewModel
34-
let onClose: (() -> Void)?
35-
let namespace: Namespace.ID
34+
@Environment(\.dismiss) private var dismiss
3635

3736
var body: some View {
38-
Group {
37+
ZStack {
3938
ScrollView {
4039
VStack(alignment: .leading, spacing: Constants.spacing) {
41-
VerticalItemView(
42-
viewModel: viewModel.verticalItemViewModel,
43-
namespace: namespace
44-
)
45-
.matchedGeometryEffect(
46-
id: MatchedGeometryEffectIdentifier(.info, for: viewModel.id),
47-
in: namespace
48-
)
40+
VerticalItemView(viewModel: viewModel.verticalItemViewModel)
4941
makeTagsChipsScrollView()
5042
ingredientsSection
5143
instructionsSection
5244
}
45+
.background(Color(.systemBackground))
5346
}
5447
.ignoresSafeArea(edges: .top)
48+
.safeAreaInset(edge: .top) {
49+
HStack {
50+
makeBackButton()
51+
Spacer()
52+
makeLikeButton()
53+
}
54+
.padding(.horizontal, Constants.spacing)
55+
.padding(.vertical, Constants.spacing)
56+
}
5557
}
58+
.navigationBarHidden(true)
5659
.background(Color(.systemBackground))
57-
.onFirstAppear {
60+
.onAppear {
5861
viewModel.fetchMeal()
5962
}
60-
.overlay(
61-
HStack(alignment: .top, spacing: .zero) {
62-
makeLikeButton()
63-
Spacer()
64-
makeCloseButton()
65-
}
66-
.padding(.horizontal, Constants.spacing)
67-
.padding(.top, Constants.spacing)
68-
.transaction { transaction in
69-
transaction.animation = nil
70-
},
71-
alignment: .top
72-
)
7363
}
7464
}
7565

@@ -95,7 +85,6 @@ private extension DetailsView {
9585
Text(Constants.Title.ingredients)
9686
.font(.headline)
9787
.padding(.top)
98-
9988
ForEach(viewModel.ingredients, id: \.self) { ingredient in
10089
HStack {
10190
Text(ingredient.name)
@@ -117,6 +106,9 @@ private extension DetailsView {
117106
if let instructions = viewModel.instructions {
118107
VStack {
119108
makeInstructionsContent(instructions: instructions)
109+
if let url = viewModel.youtubeURL {
110+
makeYoutubeLink(url: url)
111+
}
120112
}
121113
.padding(Constants.spacing)
122114
.frame(maxWidth: .infinity)
@@ -125,22 +117,26 @@ private extension DetailsView {
125117
}
126118

127119
@ViewBuilder
128-
func makeCloseButton() -> some View {
129-
if !viewModel.isCloseButtonHidden {
130-
Button(action: { onClose?() }) {
131-
ZStack {
132-
Circle()
133-
.fill(Color.black.opacity(0.3))
134-
.shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 2)
135-
136-
Image(systemName: Constants.Icons.close)
137-
.font(.title)
138-
.symbolVariant(viewModel.isLiked ? .fill : .none)
139-
.padding(12)
140-
.foregroundColor(.white)
141-
}
142-
.frame(width: Constants.closeButtonSize, height: Constants.closeButtonSize)
120+
func makeBackButton() -> some View {
121+
Button(action: { dismiss() }) {
122+
ZStack {
123+
Circle()
124+
.fill(Color.black.opacity(0.3))
125+
.shadow(
126+
color: Color.black.opacity(0.1),
127+
radius: 4,
128+
x: .zero,
129+
y: 2
130+
)
131+
Image(systemName: Constants.Icons.back)
132+
.font(.title)
133+
.padding(12)
134+
.foregroundColor(.white)
143135
}
136+
.frame(
137+
width: Constants.backButtonSize,
138+
height: Constants.backButtonSize
139+
)
144140
}
145141
}
146142

@@ -160,19 +156,13 @@ private extension DetailsView {
160156
VStack(alignment: .leading, spacing: Constants.spacing) {
161157
Text(Constants.Title.instructions)
162158
.font(.headline)
163-
.frame(maxWidth: .infinity, alignment: .leading)
164-
159+
.frame(alignment: .leading)
165160
Text(instructions)
166161
.font(.body)
167162
.multilineTextAlignment(.leading)
168-
.frame(maxWidth: .infinity, alignment: .leading)
169-
170-
if let url = viewModel.youtubeURL {
171-
makeYoutubeLink(url: url)
172-
}
163+
.frame(alignment: .leading)
173164
}
174165
.padding(Constants.spacing)
175-
.frame(maxWidth: .infinity)
176166
.background(Color.secondary.opacity(0.1))
177167
.cornerRadius(Constants.spacing)
178168
}
@@ -185,10 +175,9 @@ private extension DetailsView {
185175
Text(Constants.Title.youtube)
186176
.bold()
187177
}
188-
.frame(maxWidth: .infinity)
189178
.padding(Constants.spacing)
190-
.foregroundColor(.accent)
191-
.background(Color.white)
179+
.foregroundColor(.white)
180+
.background(Color.accentColor)
192181
.cornerRadius(Constants.spacing)
193182
}
194183
}
@@ -197,7 +186,6 @@ private extension DetailsView {
197186
// MARK: - Preview
198187

199188
#Preview("Meal Details") {
200-
@Previewable @Namespace var namespace
201189
let requester = APIRequester()
202190
let mealsService = MealsService(requester: requester)
203191
let favoritesManager = FavoritesManager(store: UserDefaults.standard)
@@ -228,9 +216,7 @@ private extension DetailsView {
228216
youtubeURL: URL(string: "example.com"),
229217
favoritesManager: favoritesManager,
230218
mealsService: mealsService
231-
),
232-
onClose: nil,
233-
namespace: namespace
219+
)
234220
)
235221
}
236222
}

EatHub/EatHub/Modules/Details/DetailsViewModel.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ final class DetailsViewModel: ObservableObject {
2020
@Published var ingredients: [Ingredient]
2121
@Published var youtubeURL: URL?
2222

23-
@Published var isCloseButtonHidden: Bool
23+
@Published var isBackButtonHidden: Bool
2424
@Published var isSkeletonable: Bool
2525

2626
var isLiked: Bool {
@@ -53,7 +53,7 @@ final class DetailsViewModel: ObservableObject {
5353
youtubeURL: URL? = nil,
5454
favoritesManager: FavoritesManagerInterface,
5555
mealsService: MealsServiceInterface,
56-
isCloseButtonHidden: Bool = false,
56+
isBackButtonHidden: Bool = false,
5757
isSkeletonable: Bool = true
5858
) {
5959
self.id = id
@@ -67,7 +67,7 @@ final class DetailsViewModel: ObservableObject {
6767
self.youtubeURL = youtubeURL
6868
self.favoritesManager = favoritesManager
6969
self.mealsService = mealsService
70-
self.isCloseButtonHidden = isCloseButtonHidden
70+
self.isBackButtonHidden = isBackButtonHidden
7171
self.isSkeletonable = isSkeletonable
7272
}
7373

EatHub/EatHub/Modules/Favorite/FavoriteView.swift

Lines changed: 23 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,23 @@ struct FavoriteView: View {
1717
}
1818

1919
var body: some View {
20-
NavigationView {
21-
ZStack {
22-
contentView
23-
.background(Color(.systemGroupedBackground))
24-
.onAppear {
25-
viewModel.refreshFavorites()
26-
}
27-
28-
if let selectedItem, showDetail {
29-
openDetailsView(for: selectedItem)
20+
NavigationStack {
21+
contentView
22+
.background(Color(.systemGroupedBackground))
23+
.onAppear {
24+
viewModel.refreshFavorites()
3025
}
31-
}
32-
.navigationBarHidden(true)
3326
}
27+
.navigationDestination(for: RecipeViewModel.self) { recipeViewModel in
28+
let input = DetailsViewModuleInput(
29+
id: recipeViewModel.id,
30+
name: recipeViewModel.name,
31+
thumbnail: recipeViewModel.thumbnail
32+
)
33+
let detailsViewModel = viewModel.detailsViewModelBuilder(input)
34+
DetailsView(viewModel: detailsViewModel)
35+
}
36+
.navigationTitle(viewModel.title)
3437
}
3538
}
3639

@@ -80,23 +83,20 @@ private extension FavoriteView {
8083
var favoritesList: some View {
8184
LazyVStack(spacing: 8) {
8285
ForEach(viewModel.likedRecipes) { recipeViewModel in
83-
RecipeRow(
84-
recipe: recipeViewModel,
85-
onToggleFavorite: {
86-
viewModel.toggleFavorite(for: recipeViewModel)
87-
}
88-
)
89-
.onTapGesture {
90-
withAnimation(.easeInOut(duration: Constants.animationDuration)) {
91-
selectedItem = recipeViewModel
92-
showDetail = true
93-
}
86+
NavigationLink(value: recipeViewModel) {
87+
RecipeRow(
88+
recipe: recipeViewModel,
89+
onToggleFavorite: {
90+
viewModel.toggleFavorite(for: recipeViewModel)
91+
}
92+
)
9493
}
9594
.buttonStyle(PlainButtonStyle())
9695
}
9796
}
9897
.padding(.top, 8)
9998
}
99+
100100
var emptyPlaceholder: some View {
101101
VStack(spacing: 16) {
102102
Image(systemName: "heart")
@@ -114,31 +114,4 @@ private extension FavoriteView {
114114
}
115115
.frame(maxWidth: .infinity, alignment: .center)
116116
}
117-
118-
@ViewBuilder
119-
func openDetailsView(for recipeViewModel: RecipeViewModel) -> some View {
120-
let viewModel = viewModel.detailsViewModelBuilder(
121-
DetailsViewModuleInput(
122-
id: recipeViewModel.id,
123-
name: recipeViewModel.name,
124-
thumbnail: recipeViewModel.thumbnail
125-
)
126-
)
127-
DetailsView(
128-
viewModel: viewModel,
129-
onClose: {
130-
withAnimation(.easeInOut(duration: Constants.animationDuration)) {
131-
showDetail = false
132-
viewModel.isCloseButtonHidden = true
133-
self.viewModel.refreshFavorites()
134-
}
135-
DispatchQueue.main.asyncAfter(deadline: .now() + Constants.animationDuration) {
136-
selectedItem = nil
137-
}
138-
},
139-
namespace: animationNamespace
140-
)
141-
.zIndex(Constants.detailZIndex)
142-
.transition(Constants.detailTransition)
143-
}
144117
}

EatHub/EatHub/Modules/Favorite/Models/RecipeViewModel.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,15 @@ class RecipeViewModel: ObservableObject, Identifiable {
1818
self.isFavorite = isFavorite
1919
}
2020
}
21+
22+
extension RecipeViewModel: Hashable {
23+
func hash(into hasher: inout Hasher) {
24+
hasher.combine(id)
25+
}
26+
}
27+
28+
extension RecipeViewModel: Equatable {
29+
static func == (lhs: RecipeViewModel, rhs: RecipeViewModel) -> Bool {
30+
lhs.id == rhs.id
31+
}
32+
}

0 commit comments

Comments
 (0)