Skip to content

Commit 6765a1f

Browse files
authored
Merge pull request #76 from YAPP-Github/TNT-227-traineeDietDetail
[TNT-227] ํŠธ๋ ˆ์ด๋‹ˆ ์‹๋‹จ ๊ธฐ๋ก ์ƒ์„ธ ํ™•์ธ ํ™”๋ฉด ์ž‘์„ฑ
2 parents 97ce6bf + 3b7a9cf commit 6765a1f

File tree

5 files changed

+259
-53
lines changed

5 files changed

+259
-53
lines changed

โ€ŽTnT/Projects/Presentation/Sources/AddDietRecord/TraineeAddDietRecordFeature.swiftโ€Ž

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ public struct TraineeAddDietRecordFeature {
138138
switch action {
139139
case .view(let action):
140140
switch action {
141-
case .binding(\.dietDate),
141+
case .binding(\.dietImageData),
142+
.binding(\.dietDate),
142143
.binding(\.dietTime),
143144
.binding(\.dietType):
144145
return self.validateAllFields(&state)
@@ -209,7 +210,10 @@ public struct TraineeAddDietRecordFeature {
209210

210211
case .tapPopUpSecondaryButton(let popUp):
211212
guard popUp != nil else { return .none }
212-
return setPopUpStatus(&state, status: nil)
213+
return .concatenate(
214+
setPopUpStatus(&state, status: nil),
215+
.run{ _ in await self.dismiss() }
216+
)
213217

214218
case .tapPopUpPrimaryButton(let popUp):
215219
guard popUp != nil else { return .none }

โ€ŽTnT/Projects/Presentation/Sources/AddDietRecord/TraineeAddDietRecordView.swiftโ€Ž

Lines changed: 35 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,6 @@ public struct TraineeAddDietRecordView: View {
6767
send(.tapSubmitButton)
6868
}
6969
.padding(.horizontal, 16)
70-
71-
// TBottomButton(
72-
// title: "์™„๋ฃŒ",
73-
// isEnable: store.view_isSubmitButtonEnabled
74-
// ) {
75-
// send(.tapSubmitButton)
76-
// }
7770
}
7871
}
7972
.sheet(item: $store.view_bottomSheetItem) { item in
@@ -115,64 +108,55 @@ public struct TraineeAddDietRecordView: View {
115108
}
116109

117110
// MARK: - Sections
118-
// @ViewBuilder
119-
// private func Header() -> some View {
120-
// VStack(alignment: .leading, spacing: 8) {
121-
// Text("์˜ค๋Š˜์˜ ์‹๋‹จ์„ ๊ธฐ๋กํ•ด ์ฃผ์„ธ์š”")
122-
// .typographyStyle(.heading2, with: .neutral950)
123-
// Text("์‹๋‹จ์„ ๊ธฐ๋กํ•˜๋ฉด ํŠธ๋ ˆ์ด๋„ˆ๊ฐ€ ํ”ผ๋“œ๋ฐฑ์„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์–ด์š”")
124-
// .typographyStyle(.body2Medium, with: .neutral500)
125-
// }
126-
// .padding(20)
127-
// }
128-
129111
@ViewBuilder
130112
private func DietPhotoSection() -> some View {
131113
PhotosPicker(
132114
selection: $store.view_photoPickerItem,
133115
matching: .images,
134116
photoLibrary: .shared()
135117
) {
136-
if let imageData = store.dietImageData,
137-
let uiImage = UIImage(data: imageData) {
138-
Image(uiImage: uiImage)
139-
.resizable()
140-
.aspectRatio(1, contentMode: .fill)
141-
.frame(maxWidth: .infinity, maxHeight: .infinity)
142-
.clipShape(.rect(cornerRadius: 20))
143-
.overlay(alignment: .topTrailing) {
144-
Button(action: { send(.tapPhotoPickerDeleteButton)}) {
145-
ZStack {
146-
Circle()
147-
.fill(Color.common100.opacity(0.5))
148-
.frame(width: 24, height: 24)
149-
Image(.icnDelete)
150-
.renderingMode(.template)
151-
.resizable()
152-
.tint(.common0)
153-
.frame(width: 12, height: 12)
118+
GeometryReader { geometry in
119+
if let imageData = store.dietImageData,
120+
let uiImage = UIImage(data: imageData) {
121+
Image(uiImage: uiImage)
122+
.resizable()
123+
.aspectRatio(contentMode: .fill)
124+
.frame(width: geometry.size.width, height: geometry.size.width)
125+
.clipShape(.rect(cornerRadius: 20))
126+
.overlay(alignment: .topTrailing) {
127+
Button(action: { send(.tapPhotoPickerDeleteButton)}) {
128+
ZStack {
129+
Circle()
130+
.fill(Color.common100.opacity(0.5))
131+
.frame(width: 24, height: 24)
132+
Image(.icnDelete)
133+
.renderingMode(.template)
134+
.resizable()
135+
.tint(.common0)
136+
.frame(width: 12, height: 12)
137+
}
138+
.padding(8)
154139
}
155-
.padding(8)
156140
}
157-
}
158-
} else {
159-
ZStack {
160-
RoundedRectangle(cornerRadius: 8)
161-
.frame(maxWidth: .infinity, maxHeight: .infinity)
162-
.aspectRatio(1, contentMode: .fit)
163-
164-
VStack(spacing: 8) {
165-
Image(.icnImage)
166-
.resizable()
167-
.frame(width: 48, height: 48)
141+
} else {
142+
ZStack {
143+
RoundedRectangle(cornerRadius: 8)
144+
.frame(width: geometry.size.width, height: geometry.size.width)
168145

169-
Text("์˜ค๋Š˜ ๋จน์€ ์‹๋‹จ์„ ์ถ”๊ฐ€ํ•ด๋ณด์„ธ์š”")
170-
.typographyStyle(.body2Medium, with: .neutral400)
146+
VStack(spacing: 8) {
147+
Image(.icnImage)
148+
.resizable()
149+
.frame(width: 48, height: 48)
150+
151+
Text("์˜ค๋Š˜ ๋จน์€ ์‹๋‹จ์„ ์ถ”๊ฐ€ํ•ด๋ณด์„ธ์š”")
152+
.typographyStyle(.body2Medium, with: .neutral400)
153+
}
171154
}
172155
}
173156
}
157+
.tint(Color.neutral100)
158+
.aspectRatio(1.0, contentMode: .fit)
174159
}
175-
.tint(Color.neutral100)
176160
.padding(20)
177161
}
178162

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//
2+
// TraineeDietRecordDetailFeature.swift
3+
// Presentation
4+
//
5+
// Created by ๋ฐ•๋ฏผ์„œ on 2/12/25.
6+
// Copyright ยฉ 2025 yapp25thTeamTnT. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import ComposableArchitecture
11+
12+
import Domain
13+
14+
@Reducer
15+
public struct TraineeDietRecordDetailFeature {
16+
17+
@ObservableState
18+
public struct State: Equatable {
19+
// MARK: Data related state
20+
/// ์‹๋‹จ ID
21+
var dietId: Int
22+
/// ์‹๋‹จ ์‚ฌ์ง„ URL
23+
var dietImageURL: URL?
24+
/// ์‹๋‹จ ์œ ํ˜•
25+
var dietType: DietType?
26+
/// ์‹๋‹จ ๋‚ ์งœ
27+
var dietDate: Date?
28+
/// ์‹๋‹จ ๋ฉ”๋ชจ
29+
var dietInfo: String
30+
31+
public init(
32+
dietId: Int,
33+
dietImageURL: URL? = nil,
34+
dietType: DietType? = nil,
35+
dietDate: Date? = nil,
36+
dietInfo: String = ""
37+
) {
38+
self.dietId = dietId
39+
self.dietImageURL = dietImageURL
40+
self.dietType = dietType
41+
self.dietDate = dietDate
42+
self.dietInfo = dietInfo
43+
}
44+
}
45+
46+
public enum Action: Sendable, ViewAction {
47+
/// ๋ทฐ์—์„œ ๋ฐœ์ƒํ•œ ์•ก์…˜์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
48+
case view(View)
49+
/// api ์ฝœ ์•ก์…˜์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค
50+
case api(APIAction)
51+
/// ๋„ค๋น„๊ฒŒ์ด์…˜ ์—ฌ๋ถ€ ์„ค์ •
52+
case setNavigating
53+
54+
@CasePathable
55+
public enum View: Sendable, BindableAction {
56+
/// ๋ฐ”์ธ๋”ฉํ•  ์•ก์…˜์„ ์ฒ˜๋ฆฌ
57+
case binding(BindingAction<State>)
58+
/// ์šฐ์ธก ์ƒ๋‹จ ellipsis ๋ฒ„ํŠผ ํƒญ
59+
case tapEllipsisButton
60+
}
61+
62+
@CasePathable
63+
public enum APIAction: Sendable {
64+
/// ์‹๋‹จ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ APi
65+
case getDietRecordDetail
66+
}
67+
}
68+
69+
public init() {}
70+
71+
public var body: some ReducerOf<Self> {
72+
BindingReducer(action: \.view)
73+
74+
Reduce { state, action in
75+
switch action {
76+
77+
case .view(let action):
78+
switch action {
79+
case .binding:
80+
return .none
81+
82+
case .tapEllipsisButton:
83+
return .none
84+
}
85+
86+
case .api(let action):
87+
switch action {
88+
case .getDietRecordDetail:
89+
// TODO: API ๋‚˜์˜ค๋ฉด ์ถ”ํ›„ ์—ฐ๊ฒฐ
90+
return .none
91+
}
92+
93+
case .setNavigating:
94+
return .none
95+
}
96+
}
97+
}
98+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//
2+
// TraineeDietRecordDetailView.swift
3+
// Presentation
4+
//
5+
// Created by ๋ฐ•๋ฏผ์„œ on 2/12/25.
6+
// Copyright ยฉ 2025 yapp25thTeamTnT. All rights reserved.
7+
//
8+
9+
import SwiftUI
10+
import ComposableArchitecture
11+
import PhotosUI
12+
13+
import Domain
14+
import DesignSystem
15+
16+
/// ์‹๋‹จ ๊ธฐ๋ก์„ ์ถ”๊ฐ€ํ•˜๋Š” ํ™”๋ฉด
17+
@ViewAction(for: TraineeDietRecordDetailFeature.self)
18+
public struct TraineeDietRecordDetailView: View {
19+
20+
@Bindable public var store: StoreOf<TraineeDietRecordDetailFeature>
21+
@Environment(\.dismiss) var dismiss: DismissAction
22+
23+
/// `TraineeDietRecordDetailView` ์ƒ์„ฑ์ž
24+
/// - Parameter store: `TraineeDietRecordDetailFeature`์™€ ์—ฐ๊ฒฐ๋œ Store
25+
public init(store: StoreOf<TraineeDietRecordDetailFeature>) {
26+
self.store = store
27+
}
28+
29+
public var body: some View {
30+
VStack(spacing: 0) {
31+
TNavigation(
32+
type: .LRButtonWithTitle(
33+
leftImage: .icnArrowLeft,
34+
centerTitle: "\(store.dietDate?.toString(format: .M์›”_d์ผ) ?? "")",
35+
rightImage: .icnEllipsis
36+
),
37+
leftAction: {
38+
dismiss()
39+
},
40+
rightAction: {
41+
send(.tapEllipsisButton)
42+
}
43+
)
44+
45+
VStack(spacing: 8) {
46+
ImageSection()
47+
48+
ContentSection()
49+
50+
Spacer()
51+
}
52+
}
53+
.navigationBarBackButtonHidden()
54+
.keyboardDismissOnTap()
55+
}
56+
57+
// MARK: - Sections
58+
@ViewBuilder
59+
private func ImageSection() -> some View {
60+
AsyncImage(url: store.dietImageURL) { phase in
61+
switch phase {
62+
case .empty:
63+
if store.dietImageURL != nil {
64+
ProgressView()
65+
.tint(.red500)
66+
.padding(20)
67+
} else {
68+
EmptyView()
69+
}
70+
71+
case .success(let image):
72+
GeometryReader { geometry in
73+
image
74+
.resizable()
75+
.aspectRatio(contentMode: .fill)
76+
.frame(width: geometry.size.width, height: geometry.size.width)
77+
.clipShape(.rect(cornerRadius: 20))
78+
}
79+
.aspectRatio(1.0, contentMode: .fit)
80+
.padding(20)
81+
82+
case .failure(let error):
83+
EmptyView()
84+
85+
@unknown default:
86+
EmptyView()
87+
}
88+
}
89+
}
90+
91+
@ViewBuilder
92+
private func ContentSection() -> some View {
93+
VStack(spacing: 8) {
94+
VStack(alignment: .leading, spacing: 0) {
95+
if let chipInfo = store.dietType?.chipInfo {
96+
TChip(uiInfo: chipInfo)
97+
}
98+
HStack(spacing: 8) {
99+
Text(store.dietDate?.toString(format: .yyyyMMddSlash) ?? "")
100+
.typographyStyle(.body2Medium, with: .neutral600)
101+
Text(store.dietDate?.toString(format: .a_HHmm) ?? "")
102+
.typographyStyle(.body2Medium, with: .neutral600)
103+
Spacer()
104+
}
105+
.frame(height: 42)
106+
}
107+
108+
TDivider(height: 2, color: .neutral100)
109+
.padding(.vertical, 8)
110+
111+
Text(store.dietInfo)
112+
.typographyStyle(.body1Medium, with: .neutral800)
113+
}
114+
.padding(.horizontal, 20)
115+
}
116+
}

โ€ŽTnT/Projects/Presentation/Sources/PresentationSupport/DietType.swiftโ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,8 @@ extension DietType {
2323
"๐Ÿฐ"
2424
}
2525
}
26+
27+
var chipInfo: TChip.UIInfo {
28+
return RecordType.diet(type: self).chipInfo
29+
}
2630
}

0 commit comments

Comments
ย (0)