Skip to content

Commit 23eef08

Browse files
authored
Main UI is re-organised. #59 (#61)
1 parent 6d7b0a1 commit 23eef08

File tree

4 files changed

+193
-150
lines changed

4 files changed

+193
-150
lines changed

Surcharges/PresentationLayer/UIs/Main/Sources/MainView.swift

Lines changed: 63 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ public struct MainView<VM: MainViewModelProtocol, Router: MainRouterProtocol, Ad
2424
@StateObject private var _adsService: AdsService = AdsService()
2525

2626
private let _surchargeStatusTip = SurchargeStatusTip()
27-
private let _useLocationTip = UseLocationTip()
28-
@State private var _showLocationDeniedAlert = false
29-
@FocusState private var _isSearchTextFeildFocused: Bool
3027

3128
public init(viewModel: VM, router: Router) {
3229
__viewModel = StateObject(wrappedValue: viewModel)
@@ -48,41 +45,24 @@ public struct MainView<VM: MainViewModelProtocol, Router: MainRouterProtocol, Ad
4845
.padding([.top], 10)
4946
.padding([.leading, .trailing], 20)
5047

51-
/*
52-
// Favourite Places is currently not available.
5348
Section {
5449

55-
FavouritePlacesView()
56-
.padding([.leading, .trailing], 20)
57-
58-
} header: {
59-
Text("Favourite Places")
60-
.font(.title3)
61-
.blurBackground()
62-
}
63-
*/
64-
65-
if _viewModel.isLoading {
66-
67-
Spacer()
68-
69-
CircleProgress()
70-
71-
Spacer()
72-
73-
} else {
74-
75-
if _viewModel.noResults {
50+
if _viewModel.isLoading {
7651

77-
NoResultView(searchedText: _viewModel.searchedText)
78-
.padding(.top, 40)
79-
.padding([.leading, .trailing], 20)
52+
CircleProgress()
8053

8154
} else {
8255

83-
if !_viewModel.mainModel.places.isEmpty {
56+
if _viewModel.noResults {
57+
58+
NoResultView(searchedText: _viewModel.searchedText)
59+
.padding(.top, 40)
60+
.padding([.leading, .trailing], 20)
8461

85-
Section {
62+
} else {
63+
64+
if !_viewModel.mainModel.places.isEmpty {
65+
8666
PlacesView(
8767
mainModel: $_viewModel.mainModel,
8868
selectedPlace: { placeId in
@@ -95,132 +75,72 @@ public struct MainView<VM: MainViewModelProtocol, Router: MainRouterProtocol, Ad
9575
}
9676
)
9777

98-
} header: {
78+
} else {
9979

100-
VStack(spacing: 0) {
101-
102-
if _adsService.isShowingAds {
103-
ListAdsView(unitId: _adsService.listBannerUnitId)
104-
}
80+
WelcomeView()
81+
.padding(.top, 40)
82+
.padding([.leading, .trailing], 20)
83+
84+
FixedAdsView(isAdShowing: $_adsService.isShowingAds, unitId: _adsService.fixedBannerUnitId)
85+
.blurRoundedBackground(cornerRadius: 20)
86+
.padding([.top, .bottom], 10)
87+
.padding([.leading, .trailing], 20)
10588

106-
Text("🔎\(R.string.localizable.searchFor(_viewModel.searchedText))")
107-
.frame(maxWidth: .infinity, alignment: .leading)
108-
.padding([.leading, .trailing], 20)
109-
.padding(.top, 10)
110-
.padding(.bottom, 10)
111-
112-
}
113-
.blurBackground()
114-
11589
}
90+
}
91+
92+
}
93+
94+
} header: {
95+
96+
if !_viewModel.showWelcome {
97+
98+
VStack(spacing: 0) {
11699

117-
} else {
100+
if _adsService.isShowingAds {
101+
ListAdsView(unitId: _adsService.listBannerUnitId)
102+
}
118103

119-
WelcomeView()
120-
.padding(.top, 40)
104+
Text("🔎\(R.string.localizable.searchFor(_viewModel.searchedText))")
105+
.frame(maxWidth: .infinity, alignment: .leading)
121106
.padding([.leading, .trailing], 20)
107+
.padding(.top, 10)
108+
.padding(.bottom, 10)
122109

123-
FixedAdsView(isAdShowing: $_adsService.isShowingAds, unitId: _adsService.fixedBannerUnitId)
124-
.blurRoundedBackground(cornerRadius: 20)
125-
.padding([.top, .bottom], 10)
126-
.padding([.leading, .trailing], 20)
127-
128110
}
111+
.blurBackground()
112+
113+
} else {
114+
115+
Rectangle()
116+
.foregroundStyle(R.color.clear.color)
117+
.frame(height: 20)
118+
129119
}
130-
}
131-
}
132-
}
133-
.scrollDismissesKeyboard(.interactively)
134-
135-
HStack(spacing: 10) {
136-
137-
Button {
138-
139-
if _viewModel.isDeniedToUseUserLocation {
140-
_showLocationDeniedAlert.toggle()
141-
} else {
142-
143-
withAnimation {
144-
_viewModel.toggleUserLocation()
145-
}
146-
147-
_useLocationTip.invalidate(reason: .actionPerformed)
148-
149-
}
150-
151-
} label: {
152-
153-
if _viewModel.isDeniedToUseUserLocation {
154-
Image(systemName: "location.slash")
155-
.foregroundStyle(R.color.blue600.color)
156-
} else {
157-
Image(systemName: _viewModel.isUserLocationOn ? "location.fill" : "location")
158-
.foregroundStyle(R.color.blue600.color)
159-
}
160-
161-
}
162-
.buttonStyle(.plain)
163-
.contentTransition(.symbolEffect(.replace))
164-
.alert(
165-
R.string.localizable.alertUseLocationDeniedTitle(),
166-
isPresented: $_showLocationDeniedAlert
167-
) {
168-
169-
Button {
170-
171-
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
172120

173-
} label: {
174-
Text(R.string.localizable.goToSettings())
175121
}
176122

177-
Button(role: .cancel) {
178-
_showLocationDeniedAlert.toggle()
179-
} label: {
180-
Text(R.string.localizable.close())
181-
}
182-
183-
} message: {
184-
Text(R.string.localizable.alertUseLocationDeniedMessage())
185-
}
186-
.popoverTip(_useLocationTip, arrowEdge: .leading) { action in
187-
withAnimation {
188-
_viewModel.toggleUserLocation()
189-
}
190-
_useLocationTip.invalidate(reason: .actionPerformed)
191-
}
192-
193-
TextField(R.string.localizable.searchBoxPlaceholder(), text: $_viewModel.searchText)
194-
.textFieldStyle(.roundedBorder)
195-
.font(.body)
196-
.disabled(_viewModel.isLoading)
197-
.onSubmit {
198-
Task {
199-
await _viewModel.search()
200-
}
201-
}
202-
.submitLabel(.search)
203-
.focused($_isSearchTextFeildFocused)
204-
.onChange(of: _isSearchTextFeildFocused, { _, newValue in
205-
if newValue {
206-
UseLocationTip.tryToSearch.toggle()
207-
}
208-
})
209-
210-
Button {
211-
Task {
212-
await _viewModel.search()
213-
}
214-
} label: {
215-
Text(R.string.localizable.searchButtonTitle())
216-
.font(.body)
217-
.disabled(!_viewModel.canSearch)
123+
/*
124+
// Favourite Places is currently not available.
125+
Section {
126+
127+
FavouritePlacesView()
128+
.padding([.leading, .trailing], 20)
129+
130+
} header: {
131+
Text("Favourite Places")
132+
.font(.title3)
133+
.blurBackground()
134+
}
135+
*/
218136
}
219-
220137
}
221-
.padding([.top], 10)
222-
.padding([.leading, .trailing], 30)
223-
.padding([.bottom], 10)
138+
.scrollDismissesKeyboard(.interactively)
139+
140+
SearchView(viewModel: __viewModel)
141+
.padding([.top], 10)
142+
.padding([.leading, .trailing], 30)
143+
.padding([.bottom], 10)
224144

225145
}
226146
.navigationTitle("Surcharges💸")
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//
2+
// SearchView.swift
3+
// Main
4+
//
5+
// Created by Bonsung Koo on 01/02/2025.
6+
// Copyright © 2025 Surcharges. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
11+
import Resources
12+
import ViewModelProtocols
13+
14+
struct SearchView<VM: MainViewModelProtocol>: View {
15+
16+
private let _useLocationTip = UseLocationTip()
17+
@State private var _showLocationDeniedAlert = false
18+
@StateObject private var _viewModel: VM
19+
20+
@FocusState private var _isSearchTextFeildFocused: Bool
21+
22+
init(viewModel: StateObject<VM>) {
23+
__viewModel = viewModel
24+
}
25+
26+
var body: some View {
27+
HStack(spacing: 10) {
28+
29+
Button {
30+
31+
if _viewModel.isDeniedToUseUserLocation {
32+
_showLocationDeniedAlert.toggle()
33+
} else {
34+
35+
withAnimation {
36+
_viewModel.toggleUserLocation()
37+
}
38+
39+
_useLocationTip.invalidate(reason: .actionPerformed)
40+
41+
}
42+
43+
} label: {
44+
45+
if _viewModel.isDeniedToUseUserLocation {
46+
Image(systemName: "location.slash")
47+
.foregroundStyle(R.color.blue600.color)
48+
} else {
49+
Image(systemName: _viewModel.isUserLocationOn ? "location.fill" : "location")
50+
.foregroundStyle(R.color.blue600.color)
51+
}
52+
53+
}
54+
.buttonStyle(.plain)
55+
.contentTransition(.symbolEffect(.replace))
56+
.alert(
57+
R.string.localizable.alertUseLocationDeniedTitle(),
58+
isPresented: $_showLocationDeniedAlert
59+
) {
60+
61+
Button {
62+
63+
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
64+
65+
} label: {
66+
Text(R.string.localizable.goToSettings())
67+
}
68+
69+
Button(role: .cancel) {
70+
_showLocationDeniedAlert.toggle()
71+
} label: {
72+
Text(R.string.localizable.close())
73+
}
74+
75+
} message: {
76+
Text(R.string.localizable.alertUseLocationDeniedMessage())
77+
}
78+
.popoverTip(_useLocationTip, arrowEdge: .leading) { action in
79+
withAnimation {
80+
_viewModel.toggleUserLocation()
81+
}
82+
_useLocationTip.invalidate(reason: .actionPerformed)
83+
}
84+
85+
TextField(R.string.localizable.searchBoxPlaceholder(), text: $_viewModel.searchText)
86+
.textFieldStyle(.roundedBorder)
87+
.font(.body)
88+
.disabled(_viewModel.isLoading)
89+
.onSubmit {
90+
91+
if _viewModel.canSearch {
92+
Task {
93+
await _viewModel.search()
94+
}
95+
}
96+
97+
}
98+
.submitLabel(.search)
99+
.focused($_isSearchTextFeildFocused)
100+
.onChange(of: _isSearchTextFeildFocused, { _, newValue in
101+
if newValue {
102+
UseLocationTip.tryToSearch.toggle()
103+
}
104+
})
105+
106+
Button {
107+
108+
Task {
109+
await _viewModel.search()
110+
}
111+
112+
} label: {
113+
Text(R.string.localizable.searchButtonTitle())
114+
.font(.body)
115+
.disabled(!_viewModel.canSearch)
116+
}
117+
118+
}
119+
}
120+
}

Surcharges/PresentationLayer/ViewModelProtocols/Sources/Main/MainViewModelProtocol.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import Models
1313
public protocol MainViewModelProtocol: ViewModelProtocol {
1414
var mainModel: MainModel { get set }
1515
var searchText: String { get set }
16-
var searchedText: String { get set }
17-
var isLoading: Bool { get set }
18-
var noResults: Bool { get set }
19-
var canSearch: Bool { get set }
20-
var isDeniedToUseUserLocation: Bool { get set }
21-
var isUserLocationOn: Bool { get set }
16+
var searchedText: String { get }
17+
var showWelcome: Bool { get }
18+
var isLoading: Bool { get }
19+
var noResults: Bool { get }
20+
var canSearch: Bool { get }
21+
var isDeniedToUseUserLocation: Bool { get }
22+
var isUserLocationOn: Bool { get }
2223

2324
func search() async
2425
func next() async

0 commit comments

Comments
 (0)