Skip to content

Commit d615750

Browse files
authored
편의점 바텀시트 구현 (#49)
* ✨ Implement convenience store selection bottom sheet view * ✨ Implement convenience store selection modal - Added a bottom sheet view to display the convenience store selection modal. * 🎨 Refactor hardcoded constants to Metrics enum * 🎨 Refactor `isPresented` state to subview - Moved isPresented state variable to HomeProductDetailSelectionView for better state management. - Renamed isPresented variable to convenienceStoreModalPresented for improved clarity and readability. * ♻️ Refactor modal presentation using `@Environment(\.dismiss)` - Replaced `@Binding` with `@Environment(\.dismiss)` for modal presentation management.
1 parent 56849bc commit d615750

File tree

7 files changed

+256
-28
lines changed

7 files changed

+256
-28
lines changed

Entity/Sources/Entity/ConvenienceStore.swift

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

11-
public enum ConvenienceStore: String, Codable {
11+
public enum ConvenienceStore: String, Codable, CaseIterable {
1212
case cu = "CU"
1313
case gs25 = "GS25"
1414
case _7Eleven = "7-ELEVEn"

PyeonHaeng-iOS.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
BA28F1A42B61572A0052855E /* Pretendard-ExtraBold.otf in Resources */ = {isa = PBXBuildFile; fileRef = BA28F19A2B61572A0052855E /* Pretendard-ExtraBold.otf */; };
3131
BA28F1A52B61572A0052855E /* Pretendard-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = BA28F19B2B61572A0052855E /* Pretendard-Light.otf */; };
3232
BA28F1A62B61572A0052855E /* Pretendard-Black.otf in Resources */ = {isa = PBXBuildFile; fileRef = BA28F19C2B61572A0052855E /* Pretendard-Black.otf */; };
33+
BA402F7E2B85E31800E86AAD /* ConvenienceSelectBottomSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA402F7D2B85E31800E86AAD /* ConvenienceSelectBottomSheetView.swift */; };
3334
BA4EA3602B6A37E10003DCE7 /* Entity in Frameworks */ = {isa = PBXBuildFile; productRef = BA4EA35F2B6A37E10003DCE7 /* Entity */; };
3435
BAA4D9AD2B5A1795005999F8 /* PyeonHaengApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA4D9AC2B5A1795005999F8 /* PyeonHaengApp.swift */; };
3536
BAA4D9AF2B5A1795005999F8 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA4D9AE2B5A1795005999F8 /* SplashView.swift */; };
@@ -87,6 +88,7 @@
8788
BA28F19B2B61572A0052855E /* Pretendard-Light.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Light.otf"; sourceTree = "<group>"; };
8889
BA28F19C2B61572A0052855E /* Pretendard-Black.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Pretendard-Black.otf"; sourceTree = "<group>"; };
8990
BA28F1A72B6157E90052855E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
91+
BA402F7D2B85E31800E86AAD /* ConvenienceSelectBottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConvenienceSelectBottomSheetView.swift; sourceTree = "<group>"; };
9092
BA4EA35A2B6A00F70003DCE7 /* Entity */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Entity; sourceTree = "<group>"; };
9193
BAA4D9A92B5A1795005999F8 /* PyeonHaeng-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PyeonHaeng-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
9294
BAA4D9AC2B5A1795005999F8 /* PyeonHaengApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PyeonHaengApp.swift; sourceTree = "<group>"; };
@@ -218,6 +220,7 @@
218220
BA28F1862B61558C0052855E /* HomeScene */ = {
219221
isa = PBXGroup;
220222
children = (
223+
BA402F7C2B85E2DE00E86AAD /* BottomSheet */,
221224
BA28F1872B6155910052855E /* HomeView.swift */,
222225
BAB5CF262B6B7CF3008B24BF /* HomeViewModel.swift */,
223226
BAE159D72B65FA6F002DCF94 /* HomeProductDetailSelectionView.swift */,
@@ -271,6 +274,14 @@
271274
path = Fonts;
272275
sourceTree = "<group>";
273276
};
277+
BA402F7C2B85E2DE00E86AAD /* BottomSheet */ = {
278+
isa = PBXGroup;
279+
children = (
280+
BA402F7D2B85E31800E86AAD /* ConvenienceSelectBottomSheetView.swift */,
281+
);
282+
path = BottomSheet;
283+
sourceTree = "<group>";
284+
};
274285
BAA4D9A02B5A1795005999F8 = {
275286
isa = PBXGroup;
276287
children = (
@@ -505,6 +516,7 @@
505516
files = (
506517
BAE159DA2B65FC35002DCF94 /* HomeProductListView.swift in Sources */,
507518
E5F2EC402B637D4A00EE0838 /* ProductInfoHeader.swift in Sources */,
519+
BA402F7E2B85E31800E86AAD /* ConvenienceSelectBottomSheetView.swift in Sources */,
508520
BA28F1852B6155810052855E /* OnboardingView.swift in Sources */,
509521
BAB5CF272B6B7CF3008B24BF /* HomeViewModel.swift in Sources */,
510522
BA28F1882B6155910052855E /* HomeView.swift in Sources */,

PyeonHaeng-iOS/Resources/Localizable.xcstrings

Lines changed: 111 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,86 @@
3939
}
4040
}
4141
},
42+
"7-Eleven" : {
43+
"localizations" : {
44+
"ja" : {
45+
"stringUnit" : {
46+
"state" : "translated",
47+
"value" : "7-Eleven"
48+
}
49+
},
50+
"ko" : {
51+
"stringUnit" : {
52+
"state" : "translated",
53+
"value" : "세븐일레븐"
54+
}
55+
}
56+
}
57+
},
58+
"CU" : {
59+
"localizations" : {
60+
"ja" : {
61+
"stringUnit" : {
62+
"state" : "translated",
63+
"value" : "CU"
64+
}
65+
},
66+
"ko" : {
67+
"stringUnit" : {
68+
"state" : "translated",
69+
"value" : "씨유"
70+
}
71+
}
72+
}
73+
},
74+
"Emart 24" : {
75+
"localizations" : {
76+
"ja" : {
77+
"stringUnit" : {
78+
"state" : "translated",
79+
"value" : "Emart 24"
80+
}
81+
},
82+
"ko" : {
83+
"stringUnit" : {
84+
"state" : "translated",
85+
"value" : "이마트 24"
86+
}
87+
}
88+
}
89+
},
90+
"GS25" : {
91+
"localizations" : {
92+
"ja" : {
93+
"stringUnit" : {
94+
"state" : "translated",
95+
"value" : "GS25"
96+
}
97+
},
98+
"ko" : {
99+
"stringUnit" : {
100+
"state" : "translated",
101+
"value" : "지에스 25"
102+
}
103+
}
104+
}
105+
},
106+
"Ministop" : {
107+
"localizations" : {
108+
"ja" : {
109+
"stringUnit" : {
110+
"state" : "translated",
111+
"value" : "Ministop"
112+
}
113+
},
114+
"ko" : {
115+
"stringUnit" : {
116+
"state" : "translated",
117+
"value" : "미니스톱"
118+
}
119+
}
120+
}
121+
},
42122
"개당" : {
43123
"extractionState" : "manual",
44124
"localizations" : {
@@ -306,47 +386,47 @@
306386
}
307387
}
308388
},
309-
"이전 행사 정보" : {
389+
"오름차순 정렬" : {
390+
"extractionState" : "manual",
310391
"localizations" : {
311392
"en" : {
312393
"stringUnit" : {
313394
"state" : "translated",
314-
"value" : "Previous Promotions"
395+
"value" : "Ascending Sorting"
315396
}
316397
},
317398
"ja" : {
318399
"stringUnit" : {
319400
"state" : "translated",
320-
"value" : "過去のプロモーション"
401+
"value" : "昇順並び替え"
321402
}
322403
},
323404
"ko" : {
324405
"stringUnit" : {
325406
"state" : "translated",
326-
"value" : "이전 행사 정보"
407+
"value" : "오름차순 정렬"
327408
}
328409
}
329410
}
330411
},
331-
"오름차순 정렬" : {
332-
"extractionState" : "manual",
412+
"이전 행사 정보" : {
333413
"localizations" : {
334414
"en" : {
335415
"stringUnit" : {
336416
"state" : "translated",
337-
"value" : "Ascending Sorting"
417+
"value" : "Previous Promotions"
338418
}
339419
},
340420
"ja" : {
341421
"stringUnit" : {
342422
"state" : "translated",
343-
"value" : "昇順並び替え"
423+
"value" : "過去のプロモーション"
344424
}
345425
},
346426
"ko" : {
347427
"stringUnit" : {
348428
"state" : "translated",
349-
"value" : "오름차순 정렬"
429+
"value" : "이전 행사 정보"
350430
}
351431
}
352432
}
@@ -420,6 +500,28 @@
420500
}
421501
}
422502
},
503+
"편의점 브랜드 선택" : {
504+
"localizations" : {
505+
"en" : {
506+
"stringUnit" : {
507+
"state" : "translated",
508+
"value" : "Choose Store Brand"
509+
}
510+
},
511+
"ja" : {
512+
"stringUnit" : {
513+
"state" : "translated",
514+
"value" : "コンビニのブランドを選ぶ"
515+
}
516+
},
517+
"ko" : {
518+
"stringUnit" : {
519+
"state" : "translated",
520+
"value" : "편의점 브랜드 선택"
521+
}
522+
}
523+
}
524+
},
423525
"행사 진행 편의점" : {
424526
"extractionState" : "manual",
425527
"localizations" : {
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//
2+
// ConvenienceSelectBottomSheetView.swift
3+
// PyeonHaeng-iOS
4+
//
5+
// Created by 홍승현 on 2/21/24.
6+
//
7+
8+
import Entity
9+
import SwiftUI
10+
11+
// MARK: - ConvenienceSelectBottomSheetView
12+
13+
struct ConvenienceSelectBottomSheetView<ViewModel>: View where ViewModel: HomeViewModelRepresentable {
14+
@EnvironmentObject private var viewModel: ViewModel
15+
@Environment(\.dismiss) private var dismiss
16+
17+
var body: some View {
18+
VStack(spacing: Metrics.itemSpacing) {
19+
Text("편의점 브랜드 선택")
20+
.font(.h3)
21+
.frame(maxWidth: .infinity, alignment: .leading)
22+
.padding(.horizontal, Metrics.horizontalPadding)
23+
24+
ForEach(ConvenienceStore.allCases, id: \.self) { store in
25+
Button {
26+
viewModel.trigger(.changeConvenienceStore(store))
27+
dismiss()
28+
} label: {
29+
ConvenienceSelectItem(convenience: store)
30+
.frame(maxWidth: .infinity, minHeight: Metrics.itemHeight, alignment: .leading)
31+
}
32+
}
33+
.padding(.horizontal, Metrics.itemHorizontalPadding)
34+
}
35+
.padding(.top, Metrics.topPadding)
36+
.padding(.bottom, Metrics.bottomPadding)
37+
}
38+
}
39+
40+
// MARK: - ConvenienceSelectItem
41+
42+
private struct ConvenienceSelectItem: View {
43+
private let convenience: ConvenienceStore
44+
45+
init(convenience: ConvenienceStore) {
46+
self.convenience = convenience
47+
}
48+
49+
var body: some View {
50+
HStack(spacing: Metrics.itemHorizontalSpacing) {
51+
convenienceImageView()
52+
convenienceText()
53+
}
54+
}
55+
56+
private func convenienceImageView() -> Image {
57+
switch convenience {
58+
case .cu:
59+
.cu
60+
case .gs25:
61+
.gs25
62+
case ._7Eleven:
63+
._7Eleven
64+
case .emart24:
65+
.emart24
66+
case .ministop:
67+
.ministop
68+
}
69+
}
70+
71+
private func convenienceText() -> Text {
72+
switch convenience {
73+
case .cu:
74+
Text("CU")
75+
case .gs25:
76+
Text("GS25")
77+
case ._7Eleven:
78+
Text("7-Eleven")
79+
case .emart24:
80+
Text("Emart 24")
81+
case .ministop:
82+
Text("Ministop")
83+
}
84+
}
85+
}
86+
87+
// MARK: - Metrics
88+
89+
private enum Metrics {
90+
static let topPadding: CGFloat = 12
91+
static let bottomPadding: CGFloat = 4
92+
static let horizontalPadding: CGFloat = 20
93+
94+
// MARK: Item
95+
96+
static let itemHorizontalPadding: CGFloat = 24
97+
static let itemHorizontalSpacing: CGFloat = 12
98+
static let itemSpacing: CGFloat = 4
99+
static let itemHeight: CGFloat = 44
100+
}

PyeonHaeng-iOS/Sources/Scenes/HomeScene/HomeProductDetailSelectionView.swift

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@ import SwiftUI
1010

1111
// MARK: - HomeProductDetailSelectionView
1212

13-
struct HomeProductDetailSelectionView: View {
13+
struct HomeProductDetailSelectionView<ViewModel>: View where ViewModel: HomeViewModelRepresentable {
14+
@EnvironmentObject private var viewModel: ViewModel
15+
@State private var convenienceStoreModalPresented: Bool = false
16+
1417
var body: some View {
1518
HStack {
16-
Button {} label: {
19+
Button {
20+
convenienceStoreModalPresented = true
21+
} label: {
1722
Group {
1823
Image.gs25
1924
.resizable()
@@ -25,6 +30,12 @@ struct HomeProductDetailSelectionView: View {
2530
}
2631
}
2732
.accessibilityHint("더블 탭하여 편의점을 선택하세요")
33+
.sheet(isPresented: $convenienceStoreModalPresented) {
34+
ConvenienceSelectBottomSheetView<ViewModel>()
35+
.presentationDetents([.height(Metrics.bottomSheetHeight)])
36+
.presentationCornerRadius(20)
37+
.presentationBackground(.regularMaterial)
38+
}
2839

2940
Spacer()
3041

@@ -57,22 +68,21 @@ struct HomeProductDetailSelectionView: View {
5768
}
5869
}
5970

60-
// MARK: HomeProductDetailSelectionView.Metrics
71+
// MARK: - Metrics
6172

62-
private extension HomeProductDetailSelectionView {
63-
enum Metrics {
64-
static let buttonSpacing: CGFloat = 2
65-
static let textHeight: CGFloat = 24
66-
static let horizontal: CGFloat = 20
67-
static let iconWidth: CGFloat = 8
68-
static let iconHeight: CGFloat = 4
73+
private enum Metrics {
74+
static let buttonSpacing: CGFloat = 2
75+
static let textHeight: CGFloat = 24
76+
static let horizontal: CGFloat = 20
77+
static let iconWidth: CGFloat = 8
78+
static let iconHeight: CGFloat = 4
6979

70-
static let promotionButtonPaddingTop: CGFloat = 4
71-
static let promotionButtonPaddingLeading: CGFloat = 16
72-
static let promotionButtonPaddingBottom: CGFloat = 4
73-
static let promotionButtonPaddingTrailing: CGFloat = 10
74-
static let promotionButtonCornerRadius: CGFloat = 16
80+
static let promotionButtonPaddingTop: CGFloat = 4
81+
static let promotionButtonPaddingLeading: CGFloat = 16
82+
static let promotionButtonPaddingBottom: CGFloat = 4
83+
static let promotionButtonPaddingTrailing: CGFloat = 10
84+
static let promotionButtonCornerRadius: CGFloat = 16
7585

76-
static let height: CGFloat = 56
77-
}
86+
static let height: CGFloat = 56
87+
static let bottomSheetHeight: CGFloat = 334
7888
}

0 commit comments

Comments
 (0)