Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TNT-227] 트레이니 식단 기록 상세 확인 화면 작성 #76

Merged
merged 2 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ public struct TraineeAddDietRecordFeature {
switch action {
case .view(let action):
switch action {
case .binding(\.dietDate),
case .binding(\.dietImageData),
.binding(\.dietDate),
.binding(\.dietTime),
.binding(\.dietType):
return self.validateAllFields(&state)
Expand Down Expand Up @@ -209,7 +210,10 @@ public struct TraineeAddDietRecordFeature {

case .tapPopUpSecondaryButton(let popUp):
guard popUp != nil else { return .none }
return setPopUpStatus(&state, status: nil)
return .concatenate(
setPopUpStatus(&state, status: nil),
.run{ _ in await self.dismiss() }
)

case .tapPopUpPrimaryButton(let popUp):
guard popUp != nil else { return .none }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,6 @@ public struct TraineeAddDietRecordView: View {
send(.tapSubmitButton)
}
.padding(.horizontal, 16)

// TBottomButton(
// title: "완료",
// isEnable: store.view_isSubmitButtonEnabled
// ) {
// send(.tapSubmitButton)
// }
}
}
.sheet(item: $store.view_bottomSheetItem) { item in
Expand Down Expand Up @@ -115,64 +108,55 @@ public struct TraineeAddDietRecordView: View {
}

// MARK: - Sections
// @ViewBuilder
// private func Header() -> some View {
// VStack(alignment: .leading, spacing: 8) {
// Text("오늘의 식단을 기록해 주세요")
// .typographyStyle(.heading2, with: .neutral950)
// Text("식단을 기록하면 트레이너가 피드백을 남길 수 있어요")
// .typographyStyle(.body2Medium, with: .neutral500)
// }
// .padding(20)
// }

@ViewBuilder
private func DietPhotoSection() -> some View {
PhotosPicker(
selection: $store.view_photoPickerItem,
matching: .images,
photoLibrary: .shared()
) {
if let imageData = store.dietImageData,
let uiImage = UIImage(data: imageData) {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(1, contentMode: .fill)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipShape(.rect(cornerRadius: 20))
.overlay(alignment: .topTrailing) {
Button(action: { send(.tapPhotoPickerDeleteButton)}) {
ZStack {
Circle()
.fill(Color.common100.opacity(0.5))
.frame(width: 24, height: 24)
Image(.icnDelete)
.renderingMode(.template)
.resizable()
.tint(.common0)
.frame(width: 12, height: 12)
GeometryReader { geometry in
if let imageData = store.dietImageData,
let uiImage = UIImage(data: imageData) {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geometry.size.width, height: geometry.size.width)
.clipShape(.rect(cornerRadius: 20))
.overlay(alignment: .topTrailing) {
Button(action: { send(.tapPhotoPickerDeleteButton)}) {
ZStack {
Circle()
.fill(Color.common100.opacity(0.5))
.frame(width: 24, height: 24)
Image(.icnDelete)
.renderingMode(.template)
.resizable()
.tint(.common0)
.frame(width: 12, height: 12)
}
.padding(8)
}
.padding(8)
}
}
} else {
ZStack {
RoundedRectangle(cornerRadius: 8)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.aspectRatio(1, contentMode: .fit)

VStack(spacing: 8) {
Image(.icnImage)
.resizable()
.frame(width: 48, height: 48)
} else {
ZStack {
RoundedRectangle(cornerRadius: 8)
.frame(width: geometry.size.width, height: geometry.size.width)

Text("오늘 먹은 식단을 추가해보세요")
.typographyStyle(.body2Medium, with: .neutral400)
VStack(spacing: 8) {
Image(.icnImage)
.resizable()
.frame(width: 48, height: 48)

Text("오늘 먹은 식단을 추가해보세요")
.typographyStyle(.body2Medium, with: .neutral400)
}
}
}
}
.tint(Color.neutral100)
.aspectRatio(1.0, contentMode: .fit)
}
.tint(Color.neutral100)
.padding(20)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// TraineeDietRecordDetailFeature.swift
// Presentation
//
// Created by 박민서 on 2/12/25.
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
//

import Foundation
import ComposableArchitecture

import Domain

@Reducer
public struct TraineeDietRecordDetailFeature {

@ObservableState
public struct State: Equatable {
// MARK: Data related state
/// 식단 ID
var dietId: Int
/// 식단 사진 URL
var dietImageURL: URL?
/// 식단 유형
var dietType: DietType?
/// 식단 날짜
var dietDate: Date?
/// 식단 메모
var dietInfo: String

public init(
dietId: Int,
dietImageURL: URL? = nil,
dietType: DietType? = nil,
dietDate: Date? = nil,
dietInfo: String = ""
) {
self.dietId = dietId
self.dietImageURL = dietImageURL
self.dietType = dietType
self.dietDate = dietDate
self.dietInfo = dietInfo
}
}

public enum Action: Sendable, ViewAction {
/// 뷰에서 발생한 액션을 처리합니다.
case view(View)
/// api 콜 액션을 처리합니다
case api(APIAction)
/// 네비게이션 여부 설정
case setNavigating

@CasePathable
public enum View: Sendable, BindableAction {
/// 바인딩할 액션을 처리
case binding(BindingAction<State>)
/// 우측 상단 ellipsis 버튼 탭
case tapEllipsisButton
}

@CasePathable
public enum APIAction: Sendable {
/// 식단 정보 가져오기 APi
case getDietRecordDetail
}
}

public init() {}

public var body: some ReducerOf<Self> {
BindingReducer(action: \.view)

Reduce { state, action in
switch action {

case .view(let action):
switch action {
case .binding:
return .none

case .tapEllipsisButton:
return .none
}

case .api(let action):
switch action {
case .getDietRecordDetail:
// TODO: API 나오면 추후 연결
return .none
}

case .setNavigating:
return .none
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//
// TraineeDietRecordDetailView.swift
// Presentation
//
// Created by 박민서 on 2/12/25.
// Copyright © 2025 yapp25thTeamTnT. All rights reserved.
//

import SwiftUI
import ComposableArchitecture
import PhotosUI

import Domain
import DesignSystem

/// 식단 기록을 추가하는 화면
@ViewAction(for: TraineeDietRecordDetailFeature.self)
public struct TraineeDietRecordDetailView: View {

@Bindable public var store: StoreOf<TraineeDietRecordDetailFeature>
@Environment(\.dismiss) var dismiss: DismissAction

/// `TraineeDietRecordDetailView` 생성자
/// - Parameter store: `TraineeDietRecordDetailFeature`와 연결된 Store
public init(store: StoreOf<TraineeDietRecordDetailFeature>) {
self.store = store
}

public var body: some View {
VStack(spacing: 0) {
TNavigation(
type: .LRButtonWithTitle(
leftImage: .icnArrowLeft,
centerTitle: "\(store.dietDate?.toString(format: .M월_d일) ?? "")",
rightImage: .icnEllipsis
),
leftAction: {
dismiss()
},
rightAction: {
send(.tapEllipsisButton)
}
)

VStack(spacing: 8) {
ImageSection()

ContentSection()

Spacer()
}
}
.navigationBarBackButtonHidden()
.keyboardDismissOnTap()
}

// MARK: - Sections
@ViewBuilder
private func ImageSection() -> some View {
AsyncImage(url: store.dietImageURL) { phase in
switch phase {
case .empty:
if store.dietImageURL != nil {
ProgressView()
.tint(.red500)
.padding(20)
} else {
EmptyView()
}

case .success(let image):
GeometryReader { geometry in
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geometry.size.width, height: geometry.size.width)
.clipShape(.rect(cornerRadius: 20))
}
.aspectRatio(1.0, contentMode: .fit)
.padding(20)

case .failure(let error):
EmptyView()

@unknown default:
EmptyView()
}
}
}

@ViewBuilder
private func ContentSection() -> some View {
VStack(spacing: 8) {
VStack(alignment: .leading, spacing: 0) {
if let chipInfo = store.dietType?.chipInfo {
TChip(uiInfo: chipInfo)
}
HStack(spacing: 8) {
Text(store.dietDate?.toString(format: .yyyyMMddSlash) ?? "")
.typographyStyle(.body2Medium, with: .neutral600)
Text(store.dietDate?.toString(format: .a_HHmm) ?? "")
.typographyStyle(.body2Medium, with: .neutral600)
Spacer()
}
.frame(height: 42)
}

TDivider(height: 2, color: .neutral100)
.padding(.vertical, 8)

Text(store.dietInfo)
.typographyStyle(.body1Medium, with: .neutral800)
}
.padding(.horizontal, 20)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ extension DietType {
"🍰"
}
}

var chipInfo: TChip.UIInfo {
return RecordType.diet(type: self).chipInfo
}
}