Skip to content

Commit 8677120

Browse files
authored
tab bar like whoosh (#100)
* tab bar whoosh * tab bar hidden
1 parent ebe7a74 commit 8677120

File tree

14 files changed

+446
-54
lines changed

14 files changed

+446
-54
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0x4E",
9+
"green" : "0x4E",
10+
"red" : "0x4E"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0x39",
27+
"green" : "0x39",
28+
"red" : "0x39"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}

EatHub/EatHub/Domain/Enums/MainTabEnum.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ enum MainTabEnum: Int, CaseIterable {
1515

1616
var imageName: String {
1717
switch self {
18-
// TODO: - SF Symbols или кастомные добавить когда найдем
1918
case .home:
2019
"frying.pan"
2120
case .search:
@@ -26,4 +25,13 @@ enum MainTabEnum: Int, CaseIterable {
2625
"shuffle"
2726
}
2827
}
28+
29+
var animationType: ColorButton.AnimationType {
30+
switch self {
31+
case .home: return .bell
32+
case .search: return .calendar
33+
case .favorites: return .plus
34+
case .random: return .gear
35+
}
36+
}
2937
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// TabBarVisibilityManager.swift
3+
// EatHub
4+
//
5+
// Created by anastasiia talmazan on 2025-04-03.
6+
//
7+
8+
import Foundation
9+
10+
class TabBarVisibilityManager: ObservableObject {
11+
@Published var isVisible: Bool = true
12+
}

EatHub/EatHub/Modules/Details/DetailsView.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ struct DetailsView: View {
2929
}
3030
}
3131

32-
@ObservedObject var viewModel: DetailsViewModel
32+
@EnvironmentObject var tabBarVisibility: TabBarVisibilityManager
33+
@StateObject var viewModel: DetailsViewModel
3334
@Environment(\.dismiss) private var dismiss
3435

3536
var body: some View {
@@ -54,12 +55,20 @@ struct DetailsView: View {
5455
.padding(.horizontal, .large)
5556
.padding(.vertical, .large)
5657
}
58+
.background(Color.Custom.backgroundPrimary)
59+
.onAppear {
60+
viewModel.fetchMeal()
61+
withAnimation {
62+
tabBarVisibility.isVisible = false
63+
}
64+
}
65+
.onDisappear {
66+
withAnimation {
67+
tabBarVisibility.isVisible = true
68+
}
69+
}
5770
}
5871
.navigationBarHidden(true)
59-
.background(Color.Custom.backgroundPrimary)
60-
.onAppear {
61-
viewModel.fetchMeal()
62-
}
6372
}
6473
}
6574

EatHub/EatHub/Modules/Favorite/FavoriteView.swift

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

33
struct FavoriteView: View {
4-
@ObservedObject var viewModel: FavoriteViewModel
4+
@EnvironmentObject var tabBarVisibility: TabBarVisibilityManager
5+
@StateObject var viewModel: FavoriteViewModel
56
@State private var selectedItem: RecipeViewModel?
67
@State private var showDetail: Bool = false
78
@Namespace private var animationNamespace
@@ -33,6 +34,7 @@ struct FavoriteView: View {
3334
)
3435
let detailsViewModel = viewModel.detailsViewModelBuilder(input)
3536
DetailsView(viewModel: detailsViewModel)
37+
.environmentObject(tabBarVisibility)
3638
}
3739
}
3840
}

EatHub/EatHub/Modules/Home/HomeView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import SwiftUI
99

1010
struct HomeView: View {
11-
@ObservedObject var viewModel: HomeViewModel
11+
@EnvironmentObject var tabBarVisibility: TabBarVisibilityManager
12+
@StateObject var viewModel: HomeViewModel
1213

1314
var body: some View {
1415
NavigationStack {
@@ -36,6 +37,7 @@ struct HomeView: View {
3637
)
3738
let detailsViewModel = viewModel.detailsViewModelBuilder(input)
3839
DetailsView(viewModel: detailsViewModel)
40+
.environmentObject(tabBarVisibility)
3941
}
4042
.onFirstAppear {
4143
viewModel.fetchMeals()

EatHub/EatHub/Modules/MainTabBar.swift

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,49 +13,68 @@ struct MainTabBar: View {
1313
@Environment(\.colorScheme) private var colorScheme
1414
@Binding var selectedIndex: MainTabEnum
1515

16-
private var color: Color {
17-
switch colorScheme {
18-
case .dark:
19-
return .white
20-
default:
21-
return .black
22-
}
23-
}
24-
2516
var body: some View {
26-
HStack(spacing: 0) {
27-
ForEach(MainTabEnum.allCases, id: \.self) { tabType in
28-
tabBarButton(for: tabType)
17+
VStack(spacing: 0) {
18+
EdgeTriangles()
19+
.fill(Color.tabBarBackground)
20+
.frame(height: 30)
21+
HStack(spacing: 0) {
22+
ForEach(Array(MainTabEnum.allCases.enumerated()), id: \.1) { index, tabType in
23+
let isSelected = selectedIndex == tabType
24+
tabBarButton(for: tabType, index: index, isSelected: isSelected)
25+
}
2926
}
27+
.background(
28+
Color.tabBarBackground
29+
)
3030
}
31-
.padding(.vertical, 20)
32-
.background(
33-
BlurView(style: .systemChromeMaterialLight)
34-
.edgesIgnoringSafeArea(.all)
31+
}
32+
}
33+
34+
struct EdgeTriangles: Shape {
35+
func path(in rect: CGRect) -> Path {
36+
var path = Path()
37+
38+
let width = rect.width
39+
let height = rect.height
40+
let triangleSize: CGFloat = 50
41+
let arcHeight: CGFloat = 1
42+
43+
path.move(to: CGPoint(x: 0, y: height))
44+
path.addLine(to: CGPoint(x: 0, y: height - triangleSize))
45+
path.addQuadCurve(
46+
to: CGPoint(x: triangleSize, y: height),
47+
control: CGPoint(x: triangleSize * 0.2, y: height - arcHeight)
3548
)
36-
.overlay(alignment: .top) {
37-
Divider()
38-
}
49+
path.closeSubpath()
50+
51+
path.move(to: CGPoint(x: width, y: height))
52+
path.addLine(to: CGPoint(x: width, y: height - triangleSize))
53+
path.addQuadCurve(
54+
to: CGPoint(x: width - triangleSize, y: height),
55+
control: CGPoint(x: width - triangleSize * 0.2, y: height - arcHeight)
56+
)
57+
path.closeSubpath()
58+
59+
return path
3960
}
4061
}
4162

4263
extension MainTabBar {
4364
@ViewBuilder
44-
private func tabBarButton(for tabType: MainTabEnum) -> some View {
45-
let isSelected = selectedIndex == tabType
46-
let foregroundColor: Color = isSelected ? .red : color
47-
let scaleEffect: CGFloat = isSelected ? 1.4 : 1.0
48-
65+
private func tabBarButton(for tabType: MainTabEnum, index: Int, isSelected: Bool) -> some View {
4966
Button {
50-
withAnimation(.spring(response: 0.3, dampingFraction: 0.5, blendDuration: 0.5)) {
67+
withAnimation {
5168
selectedIndex = tabType
5269
}
5370
} label: {
54-
Image(systemName: tabType.imageName)
55-
.foregroundColor(foregroundColor)
56-
.scaleEffect(scaleEffect)
57-
.frame(maxWidth: .infinity)
71+
ColorButton(
72+
image: Image(systemName: tabType.imageName),
73+
isSelected: isSelected,
74+
animationType: tabType.animationType
75+
)
5876
}
77+
.frame(maxWidth: .infinity)
5978
}
6079
}
6180

EatHub/EatHub/Modules/MainView.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import SwiftUI
99

1010
struct MainView: View {
1111

12+
@StateObject var tabBarVisibility = TabBarVisibilityManager()
1213
@State var selectedIndex: MainTabEnum = .home
14+
1315
var dependencies: AppDependencies
1416
private var launchScreenState: LaunchScreenStateManager
1517

@@ -24,14 +26,20 @@ struct MainView: View {
2426
TabView(selection: $selectedIndex) {
2527
HomeView(viewModel: dependencies.makeHomeViewModel())
2628
.tag(MainTabEnum.home)
29+
.environmentObject(tabBarVisibility)
2730
SearchView(viewModel: dependencies.makeSearchViewModel())
2831
.tag(MainTabEnum.search)
32+
.environmentObject(tabBarVisibility)
2933
FavoriteView(viewModel: dependencies.makeFavoriteViewModel())
3034
.tag(MainTabEnum.favorites)
35+
.environmentObject(tabBarVisibility)
3136
RandomView(viewModel: dependencies.makeRandomViewModel())
3237
.tag(MainTabEnum.random)
38+
.environmentObject(tabBarVisibility)
3339
}
40+
3441
MainTabBar(selectedIndex: $selectedIndex)
42+
.opacity(tabBarVisibility.isVisible ? 1 : 0)
3543
}
3644
.ignoresSafeArea(.keyboard)
3745
.task {

EatHub/EatHub/Modules/Random/RandomView.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ struct RandomView: View {
2727
}
2828
}
2929

30-
@ObservedObject var viewModel: RandomViewModel
30+
@EnvironmentObject var tabBarVisibility: TabBarVisibilityManager
31+
32+
@StateObject var viewModel: RandomViewModel
3133
@State private var selectedMeal: Meal?
3234
@State private var showDetail = false
3335

@@ -52,7 +54,7 @@ struct RandomView: View {
5254
RoundedRectangle(cornerRadius: Constants.buttonCorberRadius)
5355
)
5456
}
55-
.disabled(viewModel.state.isLoading)
57+
.disabled(viewModel.isLoading)
5658
}
5759
.padding(.bottom, Constants.bottomPadding)
5860
.navigationTitle(Constants.Title.navBarTitle)
@@ -61,7 +63,7 @@ struct RandomView: View {
6163
viewModel.fetchRandom()
6264
}
6365
.onShake {
64-
if !viewModel.state.isLoading {
66+
if !viewModel.isLoading {
6567
viewModel.fetchRandom()
6668
}
6769
}
@@ -73,10 +75,14 @@ struct RandomView: View {
7375
)
7476
let detailsViewModel = viewModel.detailsViewModelBuilder(input)
7577
DetailsView(viewModel: detailsViewModel)
78+
.environmentObject(tabBarVisibility)
7679
}
7780
.frame(maxWidth: .infinity)
7881
.background(Color.Custom.backgroundPrimary)
7982
}
83+
.onChange(of: viewModel.state) { _, newState in
84+
viewModel.isLoading = (newState == .loading)
85+
}
8086
}
8187
}
8288

@@ -87,8 +93,13 @@ extension RandomView {
8793
case .loading:
8894
RandomItemView()
8995
case .loaded(let result):
90-
NavigationLink(value: result) {
91-
RandomItemView(item: result)
96+
ZStack {
97+
CircleShadow(isLoading: $viewModel.isLoading)
98+
.blur(radius: 20)
99+
.allowsHitTesting(false)
100+
NavigationLink(value: result) {
101+
RandomItemView(item: result)
102+
}
92103
}
93104
case .error:
94105
CenteredVStaskText(text: Constants.Title.errorText)

EatHub/EatHub/Modules/Random/RandomViewModel.swift

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,10 @@ final class RandomViewModel: ObservableObject {
1414
case loading
1515
case loaded(Meal)
1616
case error(String)
17-
18-
var isLoading: Bool {
19-
switch self {
20-
case .loading:
21-
true
22-
default:
23-
false
24-
}
25-
}
2617
}
2718

2819
@Published var state: State = .loading
20+
@Published var isLoading = true
2921

3022
let detailsViewModelBuilder: (DetailsViewModuleInput) -> DetailsViewModel
3123
private let mealService: MealsServiceInterface

0 commit comments

Comments
 (0)