From 668083b3de09937fc76e26a33cb8c775888abb34 Mon Sep 17 00:00:00 2001 From: HISEHOONAN Date: Fri, 6 Jun 2025 18:14:53 +0900 Subject: [PATCH 1/6] [FEAT] #48 Add viewmodel function --- Dietto/Dietto.xcodeproj/project.pbxproj | 4 +- Dietto/Dietto/Data/Network/NetworkError.swift | 2 +- .../Presentation/Common/LogoProgress.swift | 2 +- .../Dietary/View/DietaryView.swift | 33 ++++--- .../Dietary/View/RecommendView.swift | 70 ++++++--------- .../Dietary/ViewModel/DietaryViewModel.swift | 86 +++++++++++-------- Dietto/Dietto/Resources/keyContainer.plist | 2 +- 7 files changed, 100 insertions(+), 99 deletions(-) diff --git a/Dietto/Dietto.xcodeproj/project.pbxproj b/Dietto/Dietto.xcodeproj/project.pbxproj index aa489a2..f673913 100644 --- a/Dietto/Dietto.xcodeproj/project.pbxproj +++ b/Dietto/Dietto.xcodeproj/project.pbxproj @@ -268,7 +268,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = UUYBHY988H; + DEVELOPMENT_TEAM = 5664C7G97A; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Dietto/Info.plist; @@ -301,7 +301,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = UUYBHY988H; + DEVELOPMENT_TEAM = 5664C7G97A; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Dietto/Info.plist; diff --git a/Dietto/Dietto/Data/Network/NetworkError.swift b/Dietto/Dietto/Data/Network/NetworkError.swift index 68aab1c..df7e933 100644 --- a/Dietto/Dietto/Data/Network/NetworkError.swift +++ b/Dietto/Dietto/Data/Network/NetworkError.swift @@ -50,7 +50,7 @@ extension NetworkError { return ToastEntity( type: .error, title: "오류 발생", - message: self.errorDescription ?? "알 수 없는 오류입니다.", + message: self.errorDescription ?? "알 수 없는 에러가 발생했습니다.", duration: 3 ) } diff --git a/Dietto/Dietto/Presentation/Common/LogoProgress.swift b/Dietto/Dietto/Presentation/Common/LogoProgress.swift index 3e1e933..983e809 100644 --- a/Dietto/Dietto/Presentation/Common/LogoProgress.swift +++ b/Dietto/Dietto/Presentation/Common/LogoProgress.swift @@ -75,7 +75,7 @@ struct LogoProgressModifier: ViewModifier { if isPresented { Rectangle() .fill(Color.black.opacity(0.3)) -// .ignoresSafeArea() + .ignoresSafeArea() LogoProgress(isAnimated: $isAnimated, message: message) .onAppear { diff --git a/Dietto/Dietto/Presentation/Dietary/View/DietaryView.swift b/Dietto/Dietto/Presentation/Dietary/View/DietaryView.swift index 6bda697..8238b62 100644 --- a/Dietto/Dietto/Presentation/Dietary/View/DietaryView.swift +++ b/Dietto/Dietto/Presentation/Dietary/View/DietaryView.swift @@ -18,8 +18,6 @@ struct DietaryView: View { @State private var myRefrigerlatorflowlayout : CGFloat = 0 //마이냉장고 @State private var isFoldMyRefrigerlator : Bool = false //마이냉장고 펼쳤다 접었다. - @State private var PushToRecommandView : Bool = false // 화면이동 - var body: some View { NavigationStack{ ZStack{ @@ -136,16 +134,20 @@ struct DietaryView: View { Spacer() - Button{ - withAnimation(.bouncy) { - isFoldMyRefrigerlator.toggle() + if !dietartViewModel.pastIngredients.isEmpty{ + Button{ + withAnimation(.bouncy) { + isFoldMyRefrigerlator.toggle() + } + }label: { + Image(systemName: isFoldMyRefrigerlator ? "chevron.down" : "chevron.up") + .frame(width: 10, height: 10) + .font(.pretendardBold20) } - }label: { - Image(systemName: isFoldMyRefrigerlator ? "chevron.down" : "chevron.up") - .frame(width: 10, height: 10) - .font(.pretendardBold20) + .foregroundStyle(.appMain) + .padding(.bottom, 8) } - .padding(.bottom, 8) + } } .padding() @@ -154,13 +156,8 @@ struct DietaryView: View { } HStack { Button("식단 추천받기") { - print("식단 추천 받기 버튼이 클릭댐") - if !dietartViewModel.presentIngredients.isEmpty { - dietartViewModel.fetchRecommendations(ingredients: dietartViewModel.presentIngredients) - PushToRecommandView = true - }else{ - print("비어있음 현재 식재료가 ") - } + dietartViewModel.fetchRecommendations(ingredients: dietartViewModel.presentIngredients) + } .font(.pretendardBold16) .foregroundStyle(.white) @@ -174,7 +171,7 @@ struct DietaryView: View { } } - .navigationDestination(isPresented: $PushToRecommandView) { + .navigationDestination(isPresented: $dietartViewModel.pushToRecommend) { RecommendView().environmentObject(dietartViewModel) } } diff --git a/Dietto/Dietto/Presentation/Dietary/View/RecommendView.swift b/Dietto/Dietto/Presentation/Dietary/View/RecommendView.swift index e48a56b..885c6c9 100644 --- a/Dietto/Dietto/Presentation/Dietary/View/RecommendView.swift +++ b/Dietto/Dietto/Presentation/Dietary/View/RecommendView.swift @@ -14,68 +14,54 @@ struct RecommendView: View { // @StateObject private var viewModel = DietaryViewModel() //디버깅용 @State private var isFoldRecommand : Bool = false // true : 펼친상태로 시작 , false: 가려진 채로 시작. -// @State private var contentHeight : CGFloat = 0 + // @State private var contentHeight : CGFloat = 0 var body: some View { ZStack { Color(.backGround).ignoresSafeArea(edges: .all) - VStack { - ContainerView(paddingSize: 16) { - HStack { - VStack { - Text("추천 레시피에 등록된 재료를 이용해 식사를 추천합니다.") - .font(.pretendardSemiBold10) - .foregroundStyle(.textFieldGray) - - ScrollView { - LazyVStack(alignment: .leading, spacing: 16) { - ForEach(viewModel.recommendList, id: \.title) { item in - VStack(alignment: .leading, spacing: 8) { - Text(item.title) - .font(.pretendardBold24) - Text(item.description) - .font(.pretendardSemiBold16) + if !viewModel.recommendList.isEmpty{ + ContainerView(paddingSize: 16) { + HStack { + VStack { + Text("추천 레시피에 등록된 재료를 이용해 식사를 추천합니다.") + .font(.pretendardSemiBold10) + .foregroundStyle(.textFieldGray) + + ScrollView { + LazyVStack(alignment: .leading, spacing: 16) { + ForEach(viewModel.recommendList, id: \.title) { item in + VStack(alignment: .leading, spacing: 8) { + Text(item.title) + .font(.pretendardBold24) + Text(item.description) + .font(.pretendardSemiBold16) + } + .padding() + .backgroundStyle(Color.textFieldGray) + .border(Color.appMain) + Divider() } - .padding() - .backgroundStyle(Color.textFieldGray) - .border(Color.appMain) - Divider() } + .padding(.horizontal) } - .padding(.horizontal) + .clipped() } - .clipped() - -// Spacer() -// -// Button { -// withAnimation(.bouncy) { -// isFoldRecommand.toggle() -// } -// } label: { -// Image(systemName: isFoldRecommand ? "chevron.up" : "chevron.down") -// .frame(width: 10, height: 10) -// .font(.pretendardBold20) -// } -// .padding(.bottom, 8) -// .border(.black) } + .padding() } - .padding() + .padding(.bottom, 10) } - .padding(.bottom, 10) + } .padding(.top, 16) - } .navigationTitle("추천 식사") .navigationBarTitleDisplayMode(.inline) .font(.pretendardBold16) - .progressOverlay(isPresented: $viewModel.isPresneted, message: "Alan이 식단을 생성하고 있어요 !") .toastView(toast: $viewModel.toast) - + .progressOverlay(isPresented: $viewModel.isPresneted, message: "Alan이 식단을 생성하고 있어요 !") } } ////디버깅용 diff --git a/Dietto/Dietto/Presentation/Dietary/ViewModel/DietaryViewModel.swift b/Dietto/Dietto/Presentation/Dietary/ViewModel/DietaryViewModel.swift index 6ea7804..bf165e1 100644 --- a/Dietto/Dietto/Presentation/Dietary/ViewModel/DietaryViewModel.swift +++ b/Dietto/Dietto/Presentation/Dietary/ViewModel/DietaryViewModel.swift @@ -9,35 +9,33 @@ import SwiftUI class DietaryViewModel: ObservableObject { @Published var isPresneted : Bool = false //로딩 여부 + @Published var pushToRecommend : Bool = false //push여부 @Published var toast: ToastEntity? //toast팝업 @Published var presentIngredients: [IngredientEntity] = [] //현재 @Published var pastIngredients : [IngredientEntity] = [ - IngredientEntity(ingredient: "오징어"), - IngredientEntity(ingredient: "꼴뚜기"), - IngredientEntity(ingredient: "홍합"), - IngredientEntity(ingredient: "닭다리"), - IngredientEntity(ingredient: "연어머리"), - IngredientEntity(ingredient: "마늘"), - IngredientEntity(ingredient: "올리브유"), - IngredientEntity(ingredient: "양파"), - IngredientEntity(ingredient: "국간장"), - IngredientEntity(ingredient: "밀가루"), - IngredientEntity(ingredient: "참기름"), - IngredientEntity(ingredient: "들기름"), - IngredientEntity(ingredient: "통후추"), - IngredientEntity(ingredient: "미역"), - IngredientEntity(ingredient: "감자"), - IngredientEntity(ingredient: "와인"), - IngredientEntity(ingredient: "당근"), - IngredientEntity(ingredient: "배추") + // IngredientEntity(ingredient: "오징어"), + // IngredientEntity(ingredient: "꼴뚜기"), + // IngredientEntity(ingredient: "홍합"), + // IngredientEntity(ingredient: "닭다리"), + // IngredientEntity(ingredient: "연어머리"), + // IngredientEntity(ingredient: "마늘"), + // IngredientEntity(ingredient: "올리브유"), + // IngredientEntity(ingredient: "양파"), + // IngredientEntity(ingredient: "국간장"), + // IngredientEntity(ingredient: "밀가루"), + // IngredientEntity(ingredient: "참기름"), + // IngredientEntity(ingredient: "들기름"), + // IngredientEntity(ingredient: "통후추"), + // IngredientEntity(ingredient: "미역"), + // IngredientEntity(ingredient: "감자"), + // IngredientEntity(ingredient: "와인"), + // IngredientEntity(ingredient: "당근"), + // IngredientEntity(ingredient: "배추") ] //추천 리스트 @Published var recommendList : [RecommendEntity] = [] -// RecommendEntity(title: "asd", description: "21124387923784235879235897"), -// RecommendEntity(title: "321123132132", description: "4539259340830459378345890"), -// RecommendEntity(title: "32112321131233132132", description: "453925934083041233123213213211231313259378345890"), -// RecommendEntity(title: "엔티티", description: "카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 카리나 ") + private let usecase : AlanUsecase //MARK: - init @@ -45,7 +43,7 @@ class DietaryViewModel: ObservableObject { self.usecase = usecase } - //MARK: - 현재 식단에 있는거 생성 + //MARK: - 현재 식재료 생성 func addpresentIngredients(_ ingredient: String) { let trimmed = ingredient.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty, @@ -58,17 +56,18 @@ class DietaryViewModel: ObservableObject { presentIngredients.removeAll { $0.id == ingredient.id } } - //MARK: - 과거 식단으로 추가 - // func addpastIngredients(ingredients: [IngredientEntity]) { - // for ingredient in ingredients { - // if let index = presentIngredients.firstIndex(where: { $0.name == ingredient.name }) { - // let removed = presentIngredients.remove(at: index) - // if !pastIngredients.contains(where: { $0.name == removed.name }) { - // pastIngredients.append(removed) - // } - // } - // } - // } + //MARK: - 현재 식재료를 삭제하고 현재 식재료를 과거 식재료로 추가 + func addpastIngredients() { + + for ingredient in self.presentIngredients { + if !self.pastIngredients.contains(where: { $0.ingredient == ingredient.ingredient }) { + self.pastIngredients.append(ingredient) + } + } + + self.presentIngredients.removeAll() + + } //MARK: - 과거 식단에 있던거 삭제 func removepastIngredients(_ ingredient: IngredientEntity) { @@ -78,20 +77,39 @@ class DietaryViewModel: ObservableObject { //MARK: - 현재 재료를 통해 식단 추천 받기. func fetchRecommendations(ingredients: [IngredientEntity]) { +// guard !presentIngredients.isEmpty else { +// self.toast = ToastEntity( +// type: .error, +// title: "에러", +// message: "현재 식재료가 비어있습니다." +// ) +// return +// } + + self.recommendList.removeAll() + isPresneted = true Task { + + self.pushToRecommend = true + do { let result = try await usecase.fetchRecommend(ingredients: ingredients) await MainActor.run { self.recommendList = result + + addpastIngredients() + self.toast = ToastEntity( type: .success, title: "완료", message: "식단 추천을 완료하였습니다.", duration: 2 ) + isPresneted = false + } } catch let error as NetworkError { await MainActor.run { diff --git a/Dietto/Dietto/Resources/keyContainer.plist b/Dietto/Dietto/Resources/keyContainer.plist index c3eb09a..4fb8329 100644 --- a/Dietto/Dietto/Resources/keyContainer.plist +++ b/Dietto/Dietto/Resources/keyContainer.plist @@ -3,6 +3,6 @@ AlanClientKey - 4b744a65-832d-4f67-868f-ad7130c02db1 + 72cbf1bc-c25f-40d4-abf0-e85e675fb7e4 From 40c142a0747f0052c296e0a8597baec4fa158e06 Mon Sep 17 00:00:00 2001 From: HISEHOONAN Date: Sat, 7 Jun 2025 14:47:11 +0900 Subject: [PATCH 2/6] [FEAT] #48 Fix LogoProgress animation Error --- .../Dietary/View/RecommendView.swift | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/Dietto/Dietto/Presentation/Dietary/View/RecommendView.swift b/Dietto/Dietto/Presentation/Dietary/View/RecommendView.swift index 885c6c9..fa7a98f 100644 --- a/Dietto/Dietto/Presentation/Dietary/View/RecommendView.swift +++ b/Dietto/Dietto/Presentation/Dietary/View/RecommendView.swift @@ -20,42 +20,39 @@ struct RecommendView: View { ZStack { Color(.backGround).ignoresSafeArea(edges: .all) VStack { - if !viewModel.recommendList.isEmpty{ - ContainerView(paddingSize: 16) { - HStack { - VStack { - Text("추천 레시피에 등록된 재료를 이용해 식사를 추천합니다.") - .font(.pretendardSemiBold10) - .foregroundStyle(.textFieldGray) - - ScrollView { - LazyVStack(alignment: .leading, spacing: 16) { - ForEach(viewModel.recommendList, id: \.title) { item in - VStack(alignment: .leading, spacing: 8) { - Text(item.title) - .font(.pretendardBold24) - Text(item.description) - .font(.pretendardSemiBold16) - } - .padding() - .backgroundStyle(Color.textFieldGray) - .border(Color.appMain) - Divider() + ContainerView(paddingSize: 16) { + HStack { + VStack { + Text("추천 레시피에 등록된 재료를 이용해 식사를 추천합니다.") + .font(.pretendardSemiBold10) + .foregroundStyle(.textFieldGray) + + ScrollView { + LazyVStack(alignment: .leading, spacing: 16) { + ForEach(viewModel.recommendList, id: \.title) { item in + VStack(alignment: .leading, spacing: 8) { + Text(item.title) + .font(.pretendardBold24) + Text(item.description) + .font(.pretendardSemiBold16) } + .padding() + .backgroundStyle(Color.textFieldGray) + .border(Color.appMain) + Divider() } - .padding(.horizontal) } - .clipped() + .padding(.horizontal) } + .clipped() } - .padding() } - .padding(.bottom, 10) + .padding() } - + .padding(.bottom, 10) } .padding(.top, 16) - + .opacity(viewModel.recommendList.isEmpty ? 0 : 1) } .navigationTitle("추천 식사") .navigationBarTitleDisplayMode(.inline) From 88a624a916ccd41820c825e4951cc4f2da29bdc2 Mon Sep 17 00:00:00 2001 From: HISEHOONAN Date: Mon, 9 Jun 2025 09:21:34 +0900 Subject: [PATCH 3/6] [FEAT] #48 Delete Annotaions --- .../Dietary/ViewModel/DietaryViewModel.swift | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Dietto/Dietto/Presentation/Dietary/ViewModel/DietaryViewModel.swift b/Dietto/Dietto/Presentation/Dietary/ViewModel/DietaryViewModel.swift index bf165e1..d415331 100644 --- a/Dietto/Dietto/Presentation/Dietary/ViewModel/DietaryViewModel.swift +++ b/Dietto/Dietto/Presentation/Dietary/ViewModel/DietaryViewModel.swift @@ -58,15 +58,12 @@ class DietaryViewModel: ObservableObject { //MARK: - 현재 식재료를 삭제하고 현재 식재료를 과거 식재료로 추가 func addpastIngredients() { - for ingredient in self.presentIngredients { if !self.pastIngredients.contains(where: { $0.ingredient == ingredient.ingredient }) { self.pastIngredients.append(ingredient) } } - self.presentIngredients.removeAll() - } //MARK: - 과거 식단에 있던거 삭제 @@ -77,15 +74,6 @@ class DietaryViewModel: ObservableObject { //MARK: - 현재 재료를 통해 식단 추천 받기. func fetchRecommendations(ingredients: [IngredientEntity]) { -// guard !presentIngredients.isEmpty else { -// self.toast = ToastEntity( -// type: .error, -// title: "에러", -// message: "현재 식재료가 비어있습니다." -// ) -// return -// } - self.recommendList.removeAll() isPresneted = true @@ -107,9 +95,7 @@ class DietaryViewModel: ObservableObject { message: "식단 추천을 완료하였습니다.", duration: 2 ) - isPresneted = false - } } catch let error as NetworkError { await MainActor.run { From d6637e1f869387d9d2f1b42c0b83a19a9317982d Mon Sep 17 00:00:00 2001 From: HISEHOONAN Date: Mon, 9 Jun 2025 09:27:21 +0900 Subject: [PATCH 4/6] [FEAT] #48 Add Ingredient Interface --- .../Domain/Usecases/IngredientUsecase.swift | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Dietto/Dietto/Domain/Usecases/IngredientUsecase.swift diff --git a/Dietto/Dietto/Domain/Usecases/IngredientUsecase.swift b/Dietto/Dietto/Domain/Usecases/IngredientUsecase.swift new file mode 100644 index 0000000..10e2694 --- /dev/null +++ b/Dietto/Dietto/Domain/Usecases/IngredientUsecase.swift @@ -0,0 +1,33 @@ +// +// IngredientUsecase.swift +// Dietto +// +// Created by 안세훈 on 6/9/25. +// +import Foundation + +//MARK: - Interface +protocol IngredientUsecase{ + func insertIngredient(_ Ingredient: IngredientEntity) async throws + func deleteIngredient(_ Ingredient: IngredientEntity) async throws + func fetchIngredient() async throws -> [IngredientEntity] +} + +//MARK: - Implement +final class IngredientUsecaseImpl : IngredientUsecase { + + func insertIngredient(_ Ingredient: IngredientEntity) async throws { + <#code#> + } + + func deleteIngredient(_ Ingredient: IngredientEntity) async throws { + <#code#> + } + + func fetchIngredient() async throws -> [IngredientEntity] { + <#code#> + } + + +} + From 1b7d5eaf197e89b34a6aeb4b122d3afbc84ff1ea Mon Sep 17 00:00:00 2001 From: JungHm Date: Mon, 9 Jun 2025 09:41:04 +0900 Subject: [PATCH 5/6] =?UTF-8?q?[FEAT]=20#48=20IngredientUsecase=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84,=20DIContainer=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dietto/Dietto/Apps/DIContainer.swift | 14 +++---- Dietto/Dietto/Data/DTO/IngredientDTO.swift | 21 ++++++++++ .../Domain/Entities/IngredientEntity.swift | 7 +++- .../Domain/Usecases/IngredientUsecase.swift | 42 +++++++++++++++---- 4 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 Dietto/Dietto/Data/DTO/IngredientDTO.swift diff --git a/Dietto/Dietto/Apps/DIContainer.swift b/Dietto/Dietto/Apps/DIContainer.swift index 8f2e737..e9da27a 100644 --- a/Dietto/Dietto/Apps/DIContainer.swift +++ b/Dietto/Dietto/Apps/DIContainer.swift @@ -11,21 +11,19 @@ import Observation final class DIContainer { private let alanUsecase: AlanUsecase private let pedometerUsecase: PedometerUsecase - private var interestsUsecase: InterestsUsecase - private var userStorageUsecase: UserStorageUsecase - private var weightHistoryUsecase: WeightHistoryUsecase + private let interestsUsecase: InterestsUsecase + private let userStorageUsecase: UserStorageUsecase + private let weightHistoryUsecase: WeightHistoryUsecase + private let ingredientUsecase: IngredientUsecase init() { self.alanUsecase = AlanUsecaseImpl(repository: NetworkRepositoryImpl()) self.pedometerUsecase = PedometerUsecaseImpl(pedometer: PedometerRepositoryImpl()) -// self.interestsUsecase = InterestsUsecaseImpl(repository: StorageRepositoryImpl()) -// self.userStorageUsecase = UserStorageUsecaseImpl(storage: StorageRepositoryImpl()) -// self.weightHistoryUsecase = WeightHistoryUsecaseImpl(repository: StorageRepositoryImpl()) - self.interestsUsecase = InterestsUsecaseImpl(repository: AnotherStorageRepositoryImpl()) self.userStorageUsecase = UserStorageUsecaseImpl(storage: AnotherStorageRepositoryImpl()) self.weightHistoryUsecase = WeightHistoryUsecaseImpl(repository: AnotherStorageRepositoryImpl()) + self.ingredientUsecase = IngredientUsecaseImpl(repository: AnotherStorageRepositoryImpl()) // Task.detached(priority: .background) { [weak self] in // self?.interestsUsecase = InterestsUsecaseImpl(repository: AnotherStorageRepositoryImpl()) @@ -52,6 +50,8 @@ final class DIContainer { func getDietaryViewModel() -> DietaryViewModel { DietaryViewModel(usecase: alanUsecase) + #warning("이렇게 주입만 하면 됌") +// DietaryViewModel(usecase: alanUsecase, ingredientUsecase: ingredientUsecase) } func getOnboardingViewModel() -> OnboardingViewModel { diff --git a/Dietto/Dietto/Data/DTO/IngredientDTO.swift b/Dietto/Dietto/Data/DTO/IngredientDTO.swift new file mode 100644 index 0000000..cf515c3 --- /dev/null +++ b/Dietto/Dietto/Data/DTO/IngredientDTO.swift @@ -0,0 +1,21 @@ +// +// IngredientDTO.swift +// Dietto +// +// Created by 안정흠 on 6/9/25. +// + + +import Foundation +import SwiftData + +@Model +final class IngredientDTO { + var id: UUID + var ingredient: String + + init(id: UUID, ingredient: String) { + self.id = id + self.ingredient = ingredient + } +} \ No newline at end of file diff --git a/Dietto/Dietto/Domain/Entities/IngredientEntity.swift b/Dietto/Dietto/Domain/Entities/IngredientEntity.swift index fb65d7a..2d6543d 100644 --- a/Dietto/Dietto/Domain/Entities/IngredientEntity.swift +++ b/Dietto/Dietto/Domain/Entities/IngredientEntity.swift @@ -9,6 +9,11 @@ import Foundation //MARK: - 식재료는 저장합니다. struct IngredientEntity: Identifiable, Hashable { - let id = UUID() + let id: UUID let ingredient: String + + init(id: UUID = UUID(), ingredient: String) { + self.id = id + self.ingredient = ingredient + } } diff --git a/Dietto/Dietto/Domain/Usecases/IngredientUsecase.swift b/Dietto/Dietto/Domain/Usecases/IngredientUsecase.swift index 10e2694..8164166 100644 --- a/Dietto/Dietto/Domain/Usecases/IngredientUsecase.swift +++ b/Dietto/Dietto/Domain/Usecases/IngredientUsecase.swift @@ -8,24 +8,50 @@ import Foundation //MARK: - Interface protocol IngredientUsecase{ - func insertIngredient(_ Ingredient: IngredientEntity) async throws - func deleteIngredient(_ Ingredient: IngredientEntity) async throws + func insertIngredient(_ ingredient: IngredientEntity) async throws + func deleteIngredient(_ ingredient: IngredientEntity) async throws func fetchIngredient() async throws -> [IngredientEntity] } //MARK: - Implement -final class IngredientUsecaseImpl : IngredientUsecase { +final class IngredientUsecaseImpl: IngredientUsecase where Repository.T == IngredientDTO { + private let repository: Repository - func insertIngredient(_ Ingredient: IngredientEntity) async throws { - <#code#> + init(repository: Repository) { + self.repository = repository } - func deleteIngredient(_ Ingredient: IngredientEntity) async throws { - <#code#> + func insertIngredient(_ ingredient: IngredientEntity) async throws { + do { + try await repository.insertData( + data: IngredientDTO(id: ingredient.id, ingredient: ingredient.ingredient) + ) + } + catch { + print(#function, error.localizedDescription) + throw StorageError.insertError + } + } + + func deleteIngredient(_ ingredient: IngredientEntity) async throws { + let id = ingredient.id + let predicate = #Predicate { $0.id == id } + do { try await repository.deleteData(where: predicate) } + catch { + print(#function, error.localizedDescription) + throw StorageError.deleteError + } } func fetchIngredient() async throws -> [IngredientEntity] { - <#code#> + do { + let result = try await repository.fetchData(where: nil, sort: []) + return result.map{ IngredientEntity(id: $0.id, ingredient: $0.ingredient) } + } + catch { + print(#function, error.localizedDescription) + throw StorageError.fetchError + } } From 844e79f4f2fca5ce62591c36a7237f7584bc9289 Mon Sep 17 00:00:00 2001 From: HISEHOONAN Date: Mon, 9 Jun 2025 10:37:41 +0900 Subject: [PATCH 6/6] [FEAT] #48 Fix Accentcolor, add Usecase --- Dietto/Dietto/Apps/DIContainer.swift | 19 +-- .../Dietary/View/DietaryView.swift | 6 +- .../Dietary/ViewModel/DietaryViewModel.swift | 118 ++++++++++++------ .../AccentColor.colorset/Contents.json | 9 ++ 4 files changed, 104 insertions(+), 48 deletions(-) diff --git a/Dietto/Dietto/Apps/DIContainer.swift b/Dietto/Dietto/Apps/DIContainer.swift index e9da27a..23befbe 100644 --- a/Dietto/Dietto/Apps/DIContainer.swift +++ b/Dietto/Dietto/Apps/DIContainer.swift @@ -25,12 +25,12 @@ final class DIContainer { self.weightHistoryUsecase = WeightHistoryUsecaseImpl(repository: AnotherStorageRepositoryImpl()) self.ingredientUsecase = IngredientUsecaseImpl(repository: AnotherStorageRepositoryImpl()) -// Task.detached(priority: .background) { [weak self] in -// self?.interestsUsecase = InterestsUsecaseImpl(repository: AnotherStorageRepositoryImpl()) -// self?.userStorageUsecase = UserStorageUsecaseImpl(storage: AnotherStorageRepositoryImpl()) -// self?.weightHistoryUsecase = WeightHistoryUsecaseImpl(repository: AnotherStorageRepositoryImpl()) -// } - + // Task.detached(priority: .background) { [weak self] in + // self?.interestsUsecase = InterestsUsecaseImpl(repository: AnotherStorageRepositoryImpl()) + // self?.userStorageUsecase = UserStorageUsecaseImpl(storage: AnotherStorageRepositoryImpl()) + // self?.weightHistoryUsecase = WeightHistoryUsecaseImpl(repository: AnotherStorageRepositoryImpl()) + // } + } func getHomeViewModel() -> HomeViewModel { @@ -49,9 +49,10 @@ final class DIContainer { } func getDietaryViewModel() -> DietaryViewModel { - DietaryViewModel(usecase: alanUsecase) - #warning("이렇게 주입만 하면 됌") -// DietaryViewModel(usecase: alanUsecase, ingredientUsecase: ingredientUsecase) + DietaryViewModel( + alanUsecase: alanUsecase, + ingredientUsecase: ingredientUsecase + ) } func getOnboardingViewModel() -> OnboardingViewModel { diff --git a/Dietto/Dietto/Presentation/Dietary/View/DietaryView.swift b/Dietto/Dietto/Presentation/Dietary/View/DietaryView.swift index 8238b62..a9c668b 100644 --- a/Dietto/Dietto/Presentation/Dietary/View/DietaryView.swift +++ b/Dietto/Dietto/Presentation/Dietary/View/DietaryView.swift @@ -178,6 +178,6 @@ struct DietaryView: View { } } -#Preview { - DietaryView(dietartViewModel: DietaryViewModel()) -} +//#Preview { +// DietaryView(dietartViewModel: DietaryViewModel()) +//} diff --git a/Dietto/Dietto/Presentation/Dietary/ViewModel/DietaryViewModel.swift b/Dietto/Dietto/Presentation/Dietary/ViewModel/DietaryViewModel.swift index d415331..4a118fd 100644 --- a/Dietto/Dietto/Presentation/Dietary/ViewModel/DietaryViewModel.swift +++ b/Dietto/Dietto/Presentation/Dietary/ViewModel/DietaryViewModel.swift @@ -12,35 +12,44 @@ class DietaryViewModel: ObservableObject { @Published var pushToRecommend : Bool = false //push여부 @Published var toast: ToastEntity? //toast팝업 @Published var presentIngredients: [IngredientEntity] = [] //현재 - @Published var pastIngredients : [IngredientEntity] = [ - // IngredientEntity(ingredient: "오징어"), - // IngredientEntity(ingredient: "꼴뚜기"), - // IngredientEntity(ingredient: "홍합"), - // IngredientEntity(ingredient: "닭다리"), - // IngredientEntity(ingredient: "연어머리"), - // IngredientEntity(ingredient: "마늘"), - // IngredientEntity(ingredient: "올리브유"), - // IngredientEntity(ingredient: "양파"), - // IngredientEntity(ingredient: "국간장"), - // IngredientEntity(ingredient: "밀가루"), - // IngredientEntity(ingredient: "참기름"), - // IngredientEntity(ingredient: "들기름"), - // IngredientEntity(ingredient: "통후추"), - // IngredientEntity(ingredient: "미역"), - // IngredientEntity(ingredient: "감자"), - // IngredientEntity(ingredient: "와인"), - // IngredientEntity(ingredient: "당근"), - // IngredientEntity(ingredient: "배추") - ] + @Published var pastIngredients : [IngredientEntity] = [] //추천 리스트 @Published var recommendList : [RecommendEntity] = [] - private let usecase : AlanUsecase + private let alanUsecase : AlanUsecase + private let ingredientUsecase : IngredientUsecase //MARK: - init - init(usecase: AlanUsecase = AlanUsecaseImpl(repository: NetworkRepositoryImpl())) { - self.usecase = usecase + init( + alanUsecase: AlanUsecase , + ingredientUsecase : IngredientUsecase + ) { + self.alanUsecase = alanUsecase + self.ingredientUsecase = ingredientUsecase + + loadPastIngredients() + } + + //MARK: - 과거에 있던 식재료 가져오기. + func loadPastIngredients() { + Task{ + do { + let result = try await ingredientUsecase.fetchIngredient() + await MainActor.run { + self.pastIngredients = result + } + } + catch { + await MainActor.run { + self.toast = ToastEntity( + type: .error, + title: "조회 실패", + message: "과거 식재료 조회에 실패하였습니다." + ) + } + } + } } //MARK: - 현재 식재료 생성 @@ -51,6 +60,7 @@ class DietaryViewModel: ObservableObject { presentIngredients.append(IngredientEntity(ingredient: trimmed)) } + //MARK: - 현재 식단에 있는거 삭제 func removepresentIngredients(_ ingredient: IngredientEntity) { presentIngredients.removeAll { $0.id == ingredient.id } @@ -58,32 +68,69 @@ class DietaryViewModel: ObservableObject { //MARK: - 현재 식재료를 삭제하고 현재 식재료를 과거 식재료로 추가 func addpastIngredients() { - for ingredient in self.presentIngredients { - if !self.pastIngredients.contains(where: { $0.ingredient == ingredient.ingredient }) { - self.pastIngredients.append(ingredient) + Task { + for ingredient in self.presentIngredients { + if !self.pastIngredients.contains(where: { $0.ingredient == ingredient.ingredient }) { + do { + try await ingredientUsecase.insertIngredient(ingredient) + await MainActor.run { + self.pastIngredients.append(ingredient) + } + } catch { + await MainActor.run { + self.toast = ToastEntity( + type: .error, + title: "저장 실패", + message: "식재료 저장 중 오류가 발생했습니다." + ) + } + } + } + } + await MainActor.run { + self.presentIngredients.removeAll() } } - self.presentIngredients.removeAll() } + //MARK: - 과거 식단에 있던거 삭제 func removepastIngredients(_ ingredient: IngredientEntity) { - pastIngredients.removeAll { $0.id == ingredient.id } + Task { + do { + try await ingredientUsecase.deleteIngredient(ingredient) + await MainActor.run { + self.pastIngredients.removeAll { $0.id == ingredient.id } + self.toast = ToastEntity( + type: .success, + title: "삭제 완료", + message: "선택한 식재료가 삭제되었습니다.", + duration: 2 + ) + } + } catch { + await MainActor.run { + self.toast = ToastEntity( + type: .error, + title: "삭제 실패", + message: "식재료 삭제 중 오류가 발생했습니다." + ) + } + } + } } + //MARK: - 현재 재료를 통해 식단 추천 받기. func fetchRecommendations(ingredients: [IngredientEntity]) { self.recommendList.removeAll() - isPresneted = true + pushToRecommend = true Task { - - self.pushToRecommend = true - do { - let result = try await usecase.fetchRecommend(ingredients: ingredients) + let result = try await alanUsecase.fetchRecommend(ingredients: ingredients) await MainActor.run { self.recommendList = result @@ -91,7 +138,7 @@ class DietaryViewModel: ObservableObject { self.toast = ToastEntity( type: .success, - title: "완료", + title: "추천 완료", message: "식단 추천을 완료하였습니다.", duration: 2 ) @@ -106,13 +153,12 @@ class DietaryViewModel: ObservableObject { await MainActor.run { self.toast = ToastEntity( type: .error, - title: "에러", + title: "추천 에러", message: error.localizedDescription ) isPresneted = false } } } - } } diff --git a/Dietto/Dietto/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/Dietto/Dietto/Resources/Assets.xcassets/AccentColor.colorset/Contents.json index eb87897..1e73b01 100644 --- a/Dietto/Dietto/Resources/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Dietto/Dietto/Resources/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,6 +1,15 @@ { "colors" : [ { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x76", + "green" : "0x76", + "red" : "0xEC" + } + }, "idiom" : "universal" } ],