From 7455b56b505f3a5ffef656e3efb6c7ed9440319e Mon Sep 17 00:00:00 2001 From: JungHm Date: Sat, 31 May 2025 22:18:23 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[FEAT]=20#39=20ModelActor=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=ED=95=9C=20StorageRepository=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/AnotherStorageRepository.swift | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift diff --git a/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift b/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift new file mode 100644 index 0000000..166c5ea --- /dev/null +++ b/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift @@ -0,0 +1,56 @@ +// +// AnotherStorageRepository.swift +// Dietto +// +// Created by 안정흠 on 5/31/25. +// + + +// +// AnotherStorageRepository.swift +// Dietto +// +// Created by 안정흠 on 5/31/25. +// + +import Foundation +import SwiftData + +protocol AnotherStorageRepository { + associatedtype T: PersistentModel + + func insertData(data: T) async throws + func updateData(predicate: Predicate, updateBlock: @escaping (T) -> Void) async throws + func fetchData(where predicate: Predicate?,sort: [SortDescriptor]) async throws -> [T] + func deleteData(where predicate: Predicate) async throws + func deleteAll() async throws +} +@ModelActor +actor AnotherStorageRepositoryImpl: AnotherStorageRepository { + + func insertData(data: T) async throws { + modelContext.insert(data) + } + func updateData(predicate: Predicate, updateBlock: @escaping (T) -> Void) async throws { + let descriptor = FetchDescriptor(predicate: predicate) + if let result = try modelContext.fetch(descriptor).first { + updateBlock(result) + } + else { // first가 없을경우 (찾는 값이 없는 경우) + + } + } + func fetchData(where predicate: Predicate? = nil, sort: [SortDescriptor] = []) async throws -> [T] { + let descriptor = FetchDescriptor(predicate: predicate, sortBy: sort) + return try modelContext.fetch(descriptor) + } + func deleteData(where predicate: Predicate) async throws { + try modelContext.delete(model: T.self, where: predicate) + } + func deleteAll() async throws { + let data = try await fetchData() + for item in data { + modelContext.delete(item) + } + } +} From da60aae9209a40184f53e2f06a04067d5e612d0e Mon Sep 17 00:00:00 2001 From: JungHm Date: Sun, 1 Jun 2025 20:50:51 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[FIX]=20#39=20Interest=20usecase=20?= =?UTF-8?q?=EB=B9=84=EB=8F=99=EA=B8=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD,=20re?= =?UTF-8?q?po=20=EC=A3=BC=EC=9E=85=EC=8B=9C=20=EB=B9=84=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=EB=A1=9C=20=EC=83=9D=EC=84=B1=20(=EB=B0=B1=EA=B7=B8=EB=9D=BC?= =?UTF-8?q?=EC=9A=B4=EB=93=9C=20=EC=9E=91=EC=97=85=20=EB=B3=B4=EC=9E=A5?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dietto/Dietto/Apps/DIContainer.swift | 10 ++++++--- .../Repository/AnotherStorageRepository.swift | 12 +++++++++- .../Domain/Usecases/InterestsUsecase.swift | 20 ++++++++--------- .../Article/View/ArticleView.swift | 12 +++++----- .../Article/View/InterestView.swift | 10 ++++----- .../Article/ViewModel/ArticleViewModel.swift | 22 +++++++++++++------ 6 files changed, 54 insertions(+), 32 deletions(-) diff --git a/Dietto/Dietto/Apps/DIContainer.swift b/Dietto/Dietto/Apps/DIContainer.swift index 4b75d1a..c7521d6 100644 --- a/Dietto/Dietto/Apps/DIContainer.swift +++ b/Dietto/Dietto/Apps/DIContainer.swift @@ -10,7 +10,7 @@ import Observation @Observable final class DIContainer { private let alanUsecase: AlanUsecase - private let interestsUsecase: InterestsUsecase + private var interestsUsecase: InterestsUsecase? private let pedometerUsecase: PedometerUsecase private let userStorageUsecase: UserStorageUsecase private let weightHistoryUsecase: WeightHistoryUsecase @@ -18,9 +18,13 @@ final class DIContainer { init() { self.alanUsecase = AlanUsecaseImpl(repository: NetworkRepositoryImpl()) self.pedometerUsecase = PedometerUsecaseImpl(pedometer: PedometerRepositoryImpl()) - self.interestsUsecase = InterestsUsecaseImpl(repository: StorageRepositoryImpl()) +// self.interestsUsecase = InterestsUsecaseImpl(repository: StorageRepositoryImpl()) self.userStorageUsecase = UserStorageUsecaseImpl(storage: StorageRepositoryImpl()) self.weightHistoryUsecase = WeightHistoryUsecaseImpl(repository: StorageRepositoryImpl()) + + Task.detached(priority: .background) { [weak self] in + self?.interestsUsecase = InterestsUsecaseImpl(repository: AnotherStorageRepositoryImpl()) + } } func getHomeViewModel() -> HomeViewModel { @@ -34,7 +38,7 @@ final class DIContainer { func getArticleViewModel() -> ArticleViewModel { ArticleViewModel( alanUsecase: alanUsecase, - storageUsecase: interestsUsecase + storageUsecase: interestsUsecase! ) } diff --git a/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift b/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift index 166c5ea..aba5945 100644 --- a/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift +++ b/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift @@ -21,13 +21,23 @@ protocol AnotherStorageRepository { func insertData(data: T) async throws func updateData(predicate: Predicate, updateBlock: @escaping (T) -> Void) async throws - func fetchData(where predicate: Predicate?,sort: [SortDescriptor]) async throws -> [T] + func fetchData(where predicate: Predicate?, sort: [SortDescriptor]) async throws -> [T] func deleteData(where predicate: Predicate) async throws func deleteAll() async throws } @ModelActor actor AnotherStorageRepositoryImpl: AnotherStorageRepository { + init() { + let configure = ModelConfiguration("\(T.self)") // 이름 지정 + do { + let modelContainer = try ModelContainer(for: T.self, configurations: configure) + self.init(modelContainer: modelContainer) + } catch { + fatalError(error.localizedDescription) + } + } + func insertData(data: T) async throws { modelContext.insert(data) } diff --git a/Dietto/Dietto/Domain/Usecases/InterestsUsecase.swift b/Dietto/Dietto/Domain/Usecases/InterestsUsecase.swift index 95d5986..29a4ef9 100644 --- a/Dietto/Dietto/Domain/Usecases/InterestsUsecase.swift +++ b/Dietto/Dietto/Domain/Usecases/InterestsUsecase.swift @@ -8,27 +8,27 @@ import Foundation protocol InterestsUsecase { - func insertInterests(_ interests: InterestEntity) - func deleteInterests(_ interests: InterestEntity) - func fetchInterests() -> [InterestEntity] + func insertInterests(_ interests: InterestEntity) async throws + func deleteInterests(_ interests: InterestEntity) async throws + func fetchInterests() async throws -> [InterestEntity] } -final class InterestsUsecaseImpl: InterestsUsecase where Repository.T == InterestsDTO { +final class InterestsUsecaseImpl: InterestsUsecase where Repository.T == InterestsDTO { private let repository: Repository init(repository: Repository) { self.repository = repository } - func insertInterests(_ interests: InterestEntity) { - repository.insertData(data: InterestsDTO(title: interests.title)) + func insertInterests(_ interests: InterestEntity) async throws { + try await repository.insertData(data: InterestsDTO(title: interests.title)) } - func deleteInterests(_ interests: InterestEntity) { + func deleteInterests(_ interests: InterestEntity) async throws { do { let title = interests.title let predicate = #Predicate { $0.title == title } - try repository.deleteData(where: predicate) + try await repository.deleteData(where: predicate) } catch { print("\(#function) : \(error.localizedDescription)") @@ -36,9 +36,9 @@ final class InterestsUsecaseImpl: InterestsUsecas } - func fetchInterests() -> [InterestEntity] { + func fetchInterests() async throws -> [InterestEntity] { do { - let result = try repository.fetchData(where: nil, sort: []) + let result = try await repository.fetchData(where: nil, sort: []) return result.map{InterestEntity(title: $0.title)} } catch { diff --git a/Dietto/Dietto/Presentation/Article/View/ArticleView.swift b/Dietto/Dietto/Presentation/Article/View/ArticleView.swift index e8e43bd..145f688 100644 --- a/Dietto/Dietto/Presentation/Article/View/ArticleView.swift +++ b/Dietto/Dietto/Presentation/Article/View/ArticleView.swift @@ -61,12 +61,12 @@ struct ArticleView: View { } } -#Preview { - NavigationView { - ArticleView(viewModel: ArticleViewModel()) - } - -} +//#Preview { +// NavigationView { +// ArticleView(viewModel: ArticleViewModel()) +// } +// +//} diff --git a/Dietto/Dietto/Presentation/Article/View/InterestView.swift b/Dietto/Dietto/Presentation/Article/View/InterestView.swift index eb3150d..c56588b 100644 --- a/Dietto/Dietto/Presentation/Article/View/InterestView.swift +++ b/Dietto/Dietto/Presentation/Article/View/InterestView.swift @@ -60,8 +60,8 @@ struct InterestsView: View { } } -#Preview { - NavigationView { - InterestsView(viewModel: ArticleViewModel()) - } -} +//#Preview { +// NavigationView { +// InterestsView(viewModel: ArticleViewModel()) +// } +//} diff --git a/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift b/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift index 721aa46..dd167aa 100644 --- a/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift +++ b/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift @@ -22,11 +22,15 @@ final class ArticleViewModel: ObservableObject { init( alanUsecase: AlanUsecase = AlanUsecaseImpl(repository: NetworkRepositoryImpl()), - storageUsecase: InterestsUsecase = InterestsUsecaseImpl(repository: StorageRepositoryImpl()) + storageUsecase: InterestsUsecase /* = InterestsUsecaseImpl(repository: AnotherStorageRepositoryImpl() */ ) { self.alanUsecase = alanUsecase self.storageUsecase = storageUsecase - selectedInterests = storageUsecase.fetchInterests() + + Task { + let result = try await storageUsecase.fetchInterests() + await MainActor.run { self.selectedInterests = result } + } } // MARK: - 아티클 로드 @@ -44,13 +48,13 @@ final class ArticleViewModel: ObservableObject { } // MARK: - 관심사 추가 / 삭제 - func addInterest(_ title: String) { + private func addInterest(_ title: String) { let entity = InterestEntity(title: title) guard !selectedInterests.contains(where: { $0.title == title }) else { return } selectedInterests.append(entity) } - func removeInterest(_ title: String) { + private func removeInterest(_ title: String) { if let index = selectedInterests.firstIndex(where: { $0.title == title }) { selectedInterests.remove(at: index) } @@ -59,11 +63,15 @@ final class ArticleViewModel: ObservableObject { func toggleInterest(_ title: String) { if selectedInterests.contains(where: { $0.title == title }) { removeInterest(title) - storageUsecase.deleteInterests(InterestEntity(title: title)) + Task { + try await storageUsecase.deleteInterests(InterestEntity(title: title)) + } } else { addInterest(title) - storageUsecase.insertInterests(InterestEntity(title: title)) + Task { + try await storageUsecase.insertInterests(InterestEntity(title: title)) + } + } - print(selectedInterests) } } From ec1e37888e985eaba9baa6c4b27db62b9562e620 Mon Sep 17 00:00:00 2001 From: JungHm Date: Sun, 1 Jun 2025 22:27:51 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[FIX]=20#39=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=AA=85=EC=8B=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20=EC=A0=95?= =?UTF-8?q?=EC=83=81=20=EB=8F=99=EC=9E=91=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dietto/Dietto.xcodeproj/project.pbxproj | 2 ++ Dietto/Dietto/Apps/DIContainer.swift | 12 ++++++------ .../Data/Repository/AnotherStorageRepository.swift | 4 ++++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Dietto/Dietto.xcodeproj/project.pbxproj b/Dietto/Dietto.xcodeproj/project.pbxproj index aa489a2..75eebf8 100644 --- a/Dietto/Dietto.xcodeproj/project.pbxproj +++ b/Dietto/Dietto.xcodeproj/project.pbxproj @@ -289,6 +289,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.justhm.Dietto; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = minimal; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -322,6 +323,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.justhm.Dietto; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_STRICT_CONCURRENCY = minimal; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/Dietto/Dietto/Apps/DIContainer.swift b/Dietto/Dietto/Apps/DIContainer.swift index c7521d6..bfe835d 100644 --- a/Dietto/Dietto/Apps/DIContainer.swift +++ b/Dietto/Dietto/Apps/DIContainer.swift @@ -10,7 +10,7 @@ import Observation @Observable final class DIContainer { private let alanUsecase: AlanUsecase - private var interestsUsecase: InterestsUsecase? + private var interestsUsecase: InterestsUsecase private let pedometerUsecase: PedometerUsecase private let userStorageUsecase: UserStorageUsecase private let weightHistoryUsecase: WeightHistoryUsecase @@ -21,10 +21,10 @@ final class DIContainer { // self.interestsUsecase = InterestsUsecaseImpl(repository: StorageRepositoryImpl()) self.userStorageUsecase = UserStorageUsecaseImpl(storage: StorageRepositoryImpl()) self.weightHistoryUsecase = WeightHistoryUsecaseImpl(repository: StorageRepositoryImpl()) - - Task.detached(priority: .background) { [weak self] in - self?.interestsUsecase = InterestsUsecaseImpl(repository: AnotherStorageRepositoryImpl()) - } + self.interestsUsecase = InterestsUsecaseImpl(repository: AnotherStorageRepositoryImpl()) +// Task.detached(priority: .background) { [weak self] in +// self?.interestsUsecase = InterestsUsecaseImpl(repository: AnotherStorageRepositoryImpl()) +// } } func getHomeViewModel() -> HomeViewModel { @@ -38,7 +38,7 @@ final class DIContainer { func getArticleViewModel() -> ArticleViewModel { ArticleViewModel( alanUsecase: alanUsecase, - storageUsecase: interestsUsecase! + storageUsecase: interestsUsecase ) } diff --git a/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift b/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift index aba5945..2d40a64 100644 --- a/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift +++ b/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift @@ -40,11 +40,13 @@ actor AnotherStorageRepositoryImpl: AnotherStorageRepository func insertData(data: T) async throws { modelContext.insert(data) + try modelContext.save() } func updateData(predicate: Predicate, updateBlock: @escaping (T) -> Void) async throws { let descriptor = FetchDescriptor(predicate: predicate) if let result = try modelContext.fetch(descriptor).first { updateBlock(result) + try modelContext.save() } else { // first가 없을경우 (찾는 값이 없는 경우) @@ -56,11 +58,13 @@ actor AnotherStorageRepositoryImpl: AnotherStorageRepository } func deleteData(where predicate: Predicate) async throws { try modelContext.delete(model: T.self, where: predicate) + try modelContext.save() } func deleteAll() async throws { let data = try await fetchData() for item in data { modelContext.delete(item) } + try modelContext.save() } } From d93b497fe647f4086bc85176146df6a6dd487a67 Mon Sep 17 00:00:00 2001 From: JungHm Date: Tue, 3 Jun 2025 15:12:23 +0900 Subject: [PATCH 04/10] [CHORE] #39 SWIFT_STRICT_CONCURRENCY = minimal --- Dietto/Dietto.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dietto/Dietto.xcodeproj/project.pbxproj b/Dietto/Dietto.xcodeproj/project.pbxproj index 75eebf8..aa489a2 100644 --- a/Dietto/Dietto.xcodeproj/project.pbxproj +++ b/Dietto/Dietto.xcodeproj/project.pbxproj @@ -289,7 +289,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.justhm.Dietto; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_STRICT_CONCURRENCY = minimal; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -323,7 +322,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.justhm.Dietto; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_STRICT_CONCURRENCY = minimal; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; From 5e3417aea2eba76b3a4ce2366d98761e784ac3b4 Mon Sep 17 00:00:00 2001 From: JungHm Date: Tue, 3 Jun 2025 15:45:59 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[FIX]=20#39=20Usecase=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EB=B9=84=EB=8F=99=EA=B8=B0=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20=EC=97=90=EB=9F=AC=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/AnotherStorageRepository.swift | 21 ++++-- .../Domain/Usecases/InterestsUsecase.swift | 22 +++--- .../Domain/Usecases/UserStorageUsecase.swift | 74 ++++++++++--------- .../Usecases/WeightHistoryUsecase.swift | 36 ++++----- 4 files changed, 84 insertions(+), 69 deletions(-) diff --git a/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift b/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift index 2d40a64..34226bb 100644 --- a/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift +++ b/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift @@ -16,6 +16,13 @@ import Foundation import SwiftData +enum StorageError: String, Error { + case insertError + case updateError + case fetchError + case deleteError +} + protocol AnotherStorageRepository { associatedtype T: PersistentModel @@ -42,24 +49,24 @@ actor AnotherStorageRepositoryImpl: AnotherStorageRepository modelContext.insert(data) try modelContext.save() } + func updateData(predicate: Predicate, updateBlock: @escaping (T) -> Void) async throws { let descriptor = FetchDescriptor(predicate: predicate) - if let result = try modelContext.fetch(descriptor).first { - updateBlock(result) - try modelContext.save() - } - else { // first가 없을경우 (찾는 값이 없는 경우) - - } + guard let result = try modelContext.fetch(descriptor).first else { throw StorageError.updateError } + updateBlock(result) + try modelContext.save() } + func fetchData(where predicate: Predicate? = nil, sort: [SortDescriptor] = []) async throws -> [T] { let descriptor = FetchDescriptor(predicate: predicate, sortBy: sort) return try modelContext.fetch(descriptor) } + func deleteData(where predicate: Predicate) async throws { try modelContext.delete(model: T.self, where: predicate) try modelContext.save() } + func deleteAll() async throws { let data = try await fetchData() for item in data { diff --git a/Dietto/Dietto/Domain/Usecases/InterestsUsecase.swift b/Dietto/Dietto/Domain/Usecases/InterestsUsecase.swift index 29a4ef9..d5f0b90 100644 --- a/Dietto/Dietto/Domain/Usecases/InterestsUsecase.swift +++ b/Dietto/Dietto/Domain/Usecases/InterestsUsecase.swift @@ -21,19 +21,21 @@ final class InterestsUsecaseImpl: Interest } func insertInterests(_ interests: InterestEntity) async throws { - try await repository.insertData(data: InterestsDTO(title: interests.title)) + do { try await repository.insertData(data: InterestsDTO(title: interests.title)) } + catch { + print(#function, error.localizedDescription) + throw StorageError.insertError + } } func deleteInterests(_ interests: InterestEntity) async throws { - do { - let title = interests.title - let predicate = #Predicate { $0.title == title } - try await repository.deleteData(where: predicate) - } + let title = interests.title + let predicate = #Predicate { $0.title == title } + do { try await repository.deleteData(where: predicate) } catch { - print("\(#function) : \(error.localizedDescription)") + print(#function, error.localizedDescription) + throw StorageError.deleteError } - } func fetchInterests() async throws -> [InterestEntity] { @@ -42,8 +44,8 @@ final class InterestsUsecaseImpl: Interest return result.map{InterestEntity(title: $0.title)} } catch { - print("\(#function) : \(error.localizedDescription)") - return [] + print(#function, error.localizedDescription) + throw StorageError.fetchError } } } diff --git a/Dietto/Dietto/Domain/Usecases/UserStorageUsecase.swift b/Dietto/Dietto/Domain/Usecases/UserStorageUsecase.swift index 092bc5f..b43467d 100644 --- a/Dietto/Dietto/Domain/Usecases/UserStorageUsecase.swift +++ b/Dietto/Dietto/Domain/Usecases/UserStorageUsecase.swift @@ -8,22 +8,22 @@ import Foundation protocol UserStorageUsecase { - func createUserData(_ user: UserEntity) - func getUserData() -> UserEntity? - func updateUserDefaultData(id: UUID, name: String, gender: Gender, height: Int) - func updateGoal(id: UUID, weight: Int, distance: Int) - func updateCurrentWeight(id: UUID, currentWeight: Int) - func deleteUserData() + func createUserData(_ user: UserEntity) async throws + func getUserData() async throws -> UserEntity + func updateUserDefaultData(id: UUID, name: String, gender: Gender, height: Int) async throws + func updateGoal(id: UUID, weight: Int, distance: Int) async throws + func updateCurrentWeight(id: UUID, currentWeight: Int) async throws + func deleteUserData() async throws } -final class UserStorageUsecaseImpl: UserStorageUsecase where Repository.T == UserDTO { +final class UserStorageUsecaseImpl: UserStorageUsecase where Repository.T == UserDTO { private let storage: Repository init(storage: Repository) { self.storage = storage } - func createUserData(_ user: UserEntity) { + func createUserData(_ user: UserEntity) async throws { let data = UserDTO( id: user.id, name: user.name, @@ -34,13 +34,17 @@ final class UserStorageUsecaseImpl: UserStorageUs targetWeight: user.targetWeight, targetDistance: user.targetDistance, ) - storage.insertData(data: data) + do { try await storage.insertData(data: data) } + catch { + print(#function, error.localizedDescription) + throw StorageError.insertError + } } - func getUserData() -> UserEntity? { + func getUserData() async throws -> UserEntity { do { - let users = try storage.fetchData(where: nil, sort: []) - guard let user = users.first else { return nil } + let users = try await storage.fetchData(where: nil, sort: []) + guard let user = users.first else { throw StorageError.fetchError } // UserDTO → UserEntity 변환 return UserEntity( id: user.id, @@ -52,60 +56,60 @@ final class UserStorageUsecaseImpl: UserStorageUs targetWeight: user.targetWeight, targetDistance: user.targetDistance, ) - } catch { - print("\(#function) : \(error.localizedDescription)") - return nil + } + catch { + print(#function, error.localizedDescription) + throw StorageError.fetchError } } - func updateUserDefaultData(id: UUID, name: String, gender: Gender, height: Int) { - let predicate = #Predicate { $0.id == id } + func updateUserDefaultData(id: UUID, name: String, gender: Gender, height: Int) async throws { do { - try storage.updateData(predicate: predicate) { dto in + let predicate = #Predicate { $0.id == id } + try await storage.updateData(predicate: predicate) { dto in dto.name = name dto.gender = gender.rawValue dto.height = height } } catch { - print("\(#function) : \(error.localizedDescription)") + print(#function, error.localizedDescription) + throw StorageError.updateError } } - func updateGoal(id: UUID, weight: Int, distance: Int) { - let predicate = #Predicate { $0.id == id } + func updateGoal(id: UUID, weight: Int, distance: Int) async throws { do { - try storage.updateData(predicate: predicate) { dto in + let predicate = #Predicate { $0.id == id } + try await storage.updateData(predicate: predicate) { dto in dto.targetWeight = weight dto.targetDistance = distance } } catch { - print("\(#function) : \(error.localizedDescription)") + print(#function, error.localizedDescription) + throw StorageError.updateError } } - func updateCurrentWeight(id: UUID, currentWeight: Int) { - let predicate = #Predicate { $0.id == id } + func updateCurrentWeight(id: UUID, currentWeight: Int) async throws { do { - try storage.updateData(predicate: predicate) { dto in + let predicate = #Predicate { $0.id == id } + try await storage.updateData(predicate: predicate) { dto in dto.currentWeight = currentWeight } } catch { - print("\(#function) : \(error.localizedDescription)") + print(#function, error.localizedDescription) + throw StorageError.deleteError } } - func deleteUserData() { - do { - try storage.deleteAll() - } + func deleteUserData() async throws { + do { try await storage.deleteAll() } catch { - print("\(#function) : \(error.localizedDescription)") + print(#function, error.localizedDescription) + throw StorageError.deleteError } - } - - } diff --git a/Dietto/Dietto/Domain/Usecases/WeightHistoryUsecase.swift b/Dietto/Dietto/Domain/Usecases/WeightHistoryUsecase.swift index 877bfd6..e314f23 100644 --- a/Dietto/Dietto/Domain/Usecases/WeightHistoryUsecase.swift +++ b/Dietto/Dietto/Domain/Usecases/WeightHistoryUsecase.swift @@ -8,40 +8,42 @@ import Foundation protocol WeightHistoryUsecase { - func addNewWeight(weight: Int, date: Date) - func getWeightHistory(chartRange: ChartTimeType) -> [WeightEntity] - func deleteAllWeightHistory() + func addNewWeight(weight: Int, date: Date) async throws + func getWeightHistory(chartRange: ChartTimeType) async throws -> [WeightEntity] + func deleteAllWeightHistory() async throws } -final class WeightHistoryUsecaseImpl: WeightHistoryUsecase where Repository.T == WeightDTO { +final class WeightHistoryUsecaseImpl: WeightHistoryUsecase where Repository.T == WeightDTO { private let storage: Repository init(repository: Repository) { self.storage = repository } - func addNewWeight(weight: Int, date: Date) { - storage.insertData(data: WeightDTO(date: date, scale: weight)) + func addNewWeight(weight: Int, date: Date) async throws { + do { try await storage.insertData(data: WeightDTO(date: date, scale: weight)) } + catch { + print(#function, error.localizedDescription) + throw StorageError.insertError + } } - func getWeightHistory(chartRange: ChartTimeType) -> [WeightEntity] { - let predicate = getDateRange(range: chartRange) - + func getWeightHistory(chartRange: ChartTimeType) async throws -> [WeightEntity] { do { - let result = try storage.fetchData(where: predicate, sort: []) + let predicate = getDateRange(range: chartRange) + let result = try await storage.fetchData(where: predicate, sort: []) return result.map{$0.convertEntity()} } catch { - print("\(#function) : \(error.localizedDescription)") - return [] + print(#function, error.localizedDescription) + throw StorageError.fetchError } } - func deleteAllWeightHistory() { - do { - try storage.deleteAll() - } + func deleteAllWeightHistory() async throws { + do { try await storage.deleteAll()} catch { - print("\(#function) : \(error.localizedDescription)") + print(#function, error.localizedDescription) + throw StorageError.deleteError } } From 21f269fc67cac539d92042e448d34383e71b7f20 Mon Sep 17 00:00:00 2001 From: JungHm Date: Tue, 3 Jun 2025 16:31:11 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[FIX]=20#39=20ViewModel=20async=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EB=8C=80=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Article/ViewModel/ArticleViewModel.swift | 19 ++- .../Presentation/Home/HomeViewModel.swift | 97 +++++++------- .../Presentation/Home/View/HomeView.swift | 31 +++-- .../Home/View/SubViews/WeightTable.swift | 6 +- .../Onboarding/View/TutorialView.swift | 6 +- .../ViewModel/OnboardingViewModel.swift | 120 ++++++++++++------ .../Profile/View/ProfileView.swift | 6 +- 7 files changed, 165 insertions(+), 120 deletions(-) diff --git a/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift b/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift index dd167aa..4dcaa1b 100644 --- a/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift +++ b/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift @@ -28,8 +28,13 @@ final class ArticleViewModel: ObservableObject { self.storageUsecase = storageUsecase Task { - let result = try await storageUsecase.fetchInterests() - await MainActor.run { self.selectedInterests = result } + do { + let result = try await storageUsecase.fetchInterests() + await MainActor.run { self.selectedInterests = result } + } + catch { +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") + } } } @@ -41,7 +46,7 @@ final class ArticleViewModel: ObservableObject { await MainActor.run{ articles = result } } catch { - +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") } } @@ -64,12 +69,16 @@ final class ArticleViewModel: ObservableObject { if selectedInterests.contains(where: { $0.title == title }) { removeInterest(title) Task { - try await storageUsecase.deleteInterests(InterestEntity(title: title)) + do { try await storageUsecase.deleteInterests(InterestEntity(title: title))} + catch { } +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") } } else { addInterest(title) Task { - try await storageUsecase.insertInterests(InterestEntity(title: title)) + do { try await storageUsecase.deleteInterests(InterestEntity(title: title)) } + catch { } +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") } } diff --git a/Dietto/Dietto/Presentation/Home/HomeViewModel.swift b/Dietto/Dietto/Presentation/Home/HomeViewModel.swift index 54e77f9..647ae4d 100644 --- a/Dietto/Dietto/Presentation/Home/HomeViewModel.swift +++ b/Dietto/Dietto/Presentation/Home/HomeViewModel.swift @@ -32,7 +32,7 @@ final class HomeViewModel { var bodyScaleHistory: [WeightEntity] = [] var pedometerData: PedometerModel? - var userData: UserEntity + var userData: UserEntity? private let pedometerUsecase: PedometerUsecase @@ -43,18 +43,24 @@ final class HomeViewModel { init( pedometerUsecase: PedometerUsecase = PedometerUsecaseImpl(pedometer: PedometerRepositoryImpl()), - weightHistroyUsecase: WeightHistoryUsecase = WeightHistoryUsecaseImpl(repository: StorageRepositoryImpl()), - userStorageUsecase: UserStorageUsecase = UserStorageUsecaseImpl(storage: StorageRepositoryImpl()) + weightHistroyUsecase: WeightHistoryUsecase, + userStorageUsecase: UserStorageUsecase ) { self.pedometerUsecase = pedometerUsecase self.weightHistroyUsecase = weightHistroyUsecase self.userStorageUsecase = userStorageUsecase - if let userData = userStorageUsecase.getUserData() { - self.userData = userData -#warning("데이터 없으면 온보딩으로") + + Task { + do { + let userData = try await userStorageUsecase.getUserData() + await MainActor.run { self.userData = userData } + } + catch { +#warning("여기에 에러핸들링 토스트 팝업 등 넣기 (데이터 없으면 온보딩으로 or fatal..?") + } } - else { fatalError("데이터 없음") } + bodyScaleHistoryFetch(type: chartTimeType) } @@ -74,27 +80,44 @@ final class HomeViewModel { } func updateCurrentBodyScale(_ value: String) { - guard let value = Int(value) else { + guard let value = Int(value), let id = userData?.id else { print("\(#function) : FAILED to update current body scale") return } + Task { + do { + try await weightHistroyUsecase.addNewWeight(weight: value, date: Date()) + try await userStorageUsecase.updateCurrentWeight(id: id, currentWeight: value) + await MainActor.run { + userData?.currentWeight = value + bodyScaleHistoryFetch(type: chartTimeType) + } + } + catch { +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") + } + } - #warning("업데이트 한 날짜가 같으면 기존 데이터 replace") - weightHistroyUsecase.addNewWeight(weight: value, date: Date()) - userStorageUsecase.updateCurrentWeight(id: userData.id, currentWeight: value) - userData.currentWeight = value - bodyScaleHistoryFetch(type: chartTimeType) } func bodyScaleHistoryFetch(type: ChartTimeType) { - chartTimeType = type - let result = weightHistroyUsecase.getWeightHistory(chartRange: type) - - if result.count >= type.limitDataCount() { - bodyScaleHistory = result - chartAnimate() + Task { + do { + let result = try await weightHistroyUsecase.getWeightHistory(chartRange: type) + await MainActor.run { + chartTimeType = type + guard result.count >= type.limitDataCount() else { + bodyScaleHistory = [] + return + } + bodyScaleHistory = result + chartAnimate() + } + } + catch { +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") + } } - else { bodyScaleHistory = [] } } private func chartAnimate() { @@ -113,37 +136,3 @@ final class HomeViewModel { } } } - - -// result.forEach { item in -// print(item) -// } -// print() -// if type == .weekly { -// result = [ -// WeightEntity(date: Date()-(86400*3), scale: 70), -// WeightEntity(date: Date()-(86400*2), scale: 65), -// WeightEntity(date: Date()-86400, scale: 55), -// WeightEntity(date: Date(), scale: 50), -// WeightEntity(date: Date()+86400, scale: 55) -// ] -// } -// else if type == .monthly { -// result = [ -// WeightEntity(date: Date()-(86400*3), scale: 70), -// WeightEntity(date: Date()-(86400*2), scale: 65), -// WeightEntity(date: Date()-86400, scale: 55), -// WeightEntity(date: Date(), scale: 50), -// WeightEntity(date: Date()+86400, scale: 55), -// WeightEntity(date: Date()+(86400*2), scale: 63), -// WeightEntity(date: Date()+(86400*3), scale: 72), -// WeightEntity(date: Date()+(86400*4), scale: 82), -// WeightEntity(date: Date()+(86400*5), scale: 72), -// WeightEntity(date: Date()+(86400*6), scale: 62), -// WeightEntity(date: Date()+(86400*7), scale: 52), -// WeightEntity(date: Date()+(86400*8), scale: 52), -// WeightEntity(date: Date()+(86400*9), scale: 52), -// WeightEntity(date: Date()+(86400*10), scale: 52), -// WeightEntity(date: Date()+(86400*11), scale: 58) -// ] -// } diff --git a/Dietto/Dietto/Presentation/Home/View/HomeView.swift b/Dietto/Dietto/Presentation/Home/View/HomeView.swift index d90a750..653b8e4 100644 --- a/Dietto/Dietto/Presentation/Home/View/HomeView.swift +++ b/Dietto/Dietto/Presentation/Home/View/HomeView.swift @@ -15,12 +15,23 @@ struct HomeView: View { VStack(spacing: 0) { HomeHeader() ScrollView { - WeightTable( - startWeight: $viewModel.userData.startWeight, - targetWeight: $viewModel.userData.targetWeight, - currentWeight: $viewModel.userData.currentWeight, - isTapModify: $isTapModify - ) + if let userData = viewModel.userData { + WeightTable( + startWeight: userData.startWeight, + targetWeight: userData.targetWeight, + currentWeight: userData.currentWeight, + isTapModify: $isTapModify + ) + } + else { + WeightTable( + startWeight: 0, + targetWeight: 0, + currentWeight: 0, + isTapModify: $isTapModify + ) + } + WeightHistoryView() .environment(viewModel) @@ -29,7 +40,7 @@ struct HomeView: View { ActivityTable( currentSteps: pedometer.steps, currentDistance: pedometer.distance, - targetDistance: viewModel.userData.targetDistance + targetDistance: viewModel.userData?.targetDistance ) } else { @@ -70,8 +81,8 @@ struct HomeView: View { } } -#Preview { - HomeView(viewModel: HomeViewModel()) -} +//#Preview { +// HomeView(viewModel: HomeViewModel()) +//} diff --git a/Dietto/Dietto/Presentation/Home/View/SubViews/WeightTable.swift b/Dietto/Dietto/Presentation/Home/View/SubViews/WeightTable.swift index 6b37627..baa1eb3 100644 --- a/Dietto/Dietto/Presentation/Home/View/SubViews/WeightTable.swift +++ b/Dietto/Dietto/Presentation/Home/View/SubViews/WeightTable.swift @@ -7,9 +7,9 @@ import SwiftUI struct WeightTable: View { - @Binding var startWeight: Int - @Binding var targetWeight: Int - @Binding var currentWeight: Int + let startWeight: Int + let targetWeight: Int + let currentWeight: Int @Binding var isTapModify: Bool diff --git a/Dietto/Dietto/Presentation/Onboarding/View/TutorialView.swift b/Dietto/Dietto/Presentation/Onboarding/View/TutorialView.swift index c60368f..0dc68a7 100644 --- a/Dietto/Dietto/Presentation/Onboarding/View/TutorialView.swift +++ b/Dietto/Dietto/Presentation/Onboarding/View/TutorialView.swift @@ -68,6 +68,6 @@ struct TutorialView: View { } -#Preview { - TutorialView(viewModel: OnboardingViewModel()) -} +//#Preview { +// TutorialView(viewModel: OnboardingViewModel()) +//} diff --git a/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift b/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift index 512dee7..f0f8c65 100644 --- a/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift +++ b/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift @@ -13,8 +13,8 @@ final class OnboardingViewModel: ObservableObject { @Published var name: String = "" @Published var gender: Gender = .male - @Published var targetWeight: Int = 60 - @Published var targetDistance: Int = 2 + @Published var targetWeight: Int = 0 + @Published var targetDistance: Int = 0 @Published var height: String = "" @Published var weight : String = "" @@ -36,22 +36,30 @@ final class OnboardingViewModel: ObservableObject { private let weightHistroyUsecase: WeightHistoryUsecase init( - weightHistroyUsecase: WeightHistoryUsecase = WeightHistoryUsecaseImpl(repository: StorageRepositoryImpl()), - userStorageUsecase: UserStorageUsecase = UserStorageUsecaseImpl(storage: StorageRepositoryImpl()) + weightHistroyUsecase: WeightHistoryUsecase, + userStorageUsecase: UserStorageUsecase ) { self.weightHistroyUsecase = weightHistroyUsecase self.userStorageUsecase = userStorageUsecase - - if let user = userStorageUsecase.getUserData() { - currentUserId = user.id - name = user.name - gender = user.gender - height = String(user.height) - weight = String(user.currentWeight) - targetWeight = user.targetWeight - targetDistance = user.targetDistance + Task { + do { + let user = try await userStorageUsecase.getUserData() + await MainActor.run { + currentUserId = user.id + name = user.name + gender = user.gender + height = String(user.height) + weight = String(user.currentWeight) + targetWeight = user.targetWeight + targetDistance = user.targetDistance + } + } + catch { + #warning("여기에 에러핸들링 토스트 팝업 등 넣기") + } } + } //MARK: - 프로필 설정 @@ -66,39 +74,60 @@ final class OnboardingViewModel: ObservableObject { //MARK: - 최초 진입 if isFirstLaunch { - let userEntity = UserEntity( - id: currentUserId ?? UUID(), - name: name, - gender: gender, - height: height, - startWeight: weight, - currentWeight: weight, - targetWeight: targetWeight, - targetDistance: targetDistance - ) - currentUserId = userEntity.id - userStorageUsecase.createUserData(userEntity) - weightHistroyUsecase.addNewWeight(weight: weight, date: Date()) - - isProfileSaved = true - - DispatchQueue.main.asyncAfter(deadline: .now() + 4) { - self.isFirstLaunch = false - self.isProfileSaved = false - } - - //MARK: - 프로필 수정 + createNewProfile(weight: weight, height: height) } else { guard let currentUserId = currentUserId else { fatalError("CurrentUser is nil") //없으면 말도 안되고 완전 꼬여버리기 때문에 일단 조치해둠 } - userStorageUsecase.updateUserDefaultData(id: currentUserId, name: name, gender: gender, height: height) - userStorageUsecase.updateGoal(id: currentUserId, weight: targetWeight, distance: targetDistance) - userStorageUsecase.updateCurrentWeight(id: currentUserId, currentWeight: weight) - isEditActive = false + editProfile(currentUserId: currentUserId, weight: weight, height: height) + } + } + + private func editProfile(currentUserId: UUID, weight: Int, height: Int) { + Task { + do { + try await userStorageUsecase.updateUserDefaultData(id: currentUserId, name: name, gender: gender, height: height) + try await userStorageUsecase.updateGoal(id: currentUserId, weight: targetWeight, distance: targetDistance) + try await userStorageUsecase.updateCurrentWeight(id: currentUserId, currentWeight: weight) + await MainActor.run { isEditActive = false } + } + catch { +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") + } } } + private func createNewProfile(weight: Int, height: Int) { + Task { + do { + let userEntity = UserEntity( + id: UUID(), + name: name, + gender: gender, + height: height, + startWeight: weight, + currentWeight: weight, + targetWeight: targetWeight, + targetDistance: targetDistance + ) + try await userStorageUsecase.createUserData(userEntity) + try await weightHistroyUsecase.addNewWeight(weight: weight, date: Date()) + await MainActor.run { + isProfileSaved = true + } + try await Task.sleep(for: .seconds(4)) + await MainActor.run { + self.isFirstLaunch = false + self.isProfileSaved = false + } + } + catch { +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") + } + } + + } + func selectGender(_ gender: Gender) { self.gender = gender } @@ -110,9 +139,16 @@ final class OnboardingViewModel: ObservableObject { } func deleteAllUserData() { - userStorageUsecase.deleteUserData() - weightHistroyUsecase.deleteAllWeightHistory() - isFirstLaunch = true + Task { + do { + try await userStorageUsecase.deleteUserData() + try await weightHistroyUsecase.deleteAllWeightHistory() + isFirstLaunch = true + } + catch { +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") + } + } } } diff --git a/Dietto/Dietto/Presentation/Profile/View/ProfileView.swift b/Dietto/Dietto/Presentation/Profile/View/ProfileView.swift index eb358ad..d9faf7d 100644 --- a/Dietto/Dietto/Presentation/Profile/View/ProfileView.swift +++ b/Dietto/Dietto/Presentation/Profile/View/ProfileView.swift @@ -164,6 +164,6 @@ struct ProfileView: View { -#Preview { - ProfileView(viewModel: OnboardingViewModel()) -} +//#Preview { +// ProfileView(viewModel: OnboardingViewModel()) +//} From 50fc064d34c135a496aaf4299bd11b2a38fde339 Mon Sep 17 00:00:00 2001 From: JungHm Date: Tue, 3 Jun 2025 16:37:55 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[FIX]=20#39=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dietto/Dietto/Apps/DIContainer.swift | 11 +- .../Presentation/Home/View/HomeView.swift | 100 +++++++++--------- 2 files changed, 55 insertions(+), 56 deletions(-) diff --git a/Dietto/Dietto/Apps/DIContainer.swift b/Dietto/Dietto/Apps/DIContainer.swift index bfe835d..0f42cce 100644 --- a/Dietto/Dietto/Apps/DIContainer.swift +++ b/Dietto/Dietto/Apps/DIContainer.swift @@ -19,12 +19,13 @@ final class DIContainer { 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.userStorageUsecase = UserStorageUsecaseImpl(storage: StorageRepositoryImpl()) +// self.weightHistoryUsecase = WeightHistoryUsecaseImpl(repository: StorageRepositoryImpl()) self.interestsUsecase = InterestsUsecaseImpl(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 { diff --git a/Dietto/Dietto/Presentation/Home/View/HomeView.swift b/Dietto/Dietto/Presentation/Home/View/HomeView.swift index 653b8e4..a35c411 100644 --- a/Dietto/Dietto/Presentation/Home/View/HomeView.swift +++ b/Dietto/Dietto/Presentation/Home/View/HomeView.swift @@ -12,72 +12,70 @@ struct HomeView: View { @State var viewModel: HomeViewModel @State var isTapModify: Bool = false var body: some View { - VStack(spacing: 0) { - HomeHeader() - ScrollView { - if let userData = viewModel.userData { + if let userData = viewModel.userData { + VStack(spacing: 0) { + HomeHeader() + ScrollView { WeightTable( startWeight: userData.startWeight, targetWeight: userData.targetWeight, currentWeight: userData.currentWeight, isTapModify: $isTapModify ) - } - else { - WeightTable( - startWeight: 0, - targetWeight: 0, - currentWeight: 0, - isTapModify: $isTapModify - ) - } - - - WeightHistoryView() - .environment(viewModel) - - if let pedometer = viewModel.pedometerData { - ActivityTable( - currentSteps: pedometer.steps, - currentDistance: pedometer.distance, - targetDistance: viewModel.userData?.targetDistance - ) - } - else { - ActivityTable(currentSteps: 10, currentDistance: 10, targetDistance: 20) - .overlay { - RoundedRectangle(cornerRadius: 21) - .fill(.ultraThinMaterial) - .foregroundStyle(.white) - .opacity(0.98) - .padding() - .overlay { - VStack { - Text("건강 앱 권한이 필요합니다.") - .font(.pretendardBold16) - .foregroundStyle(.text) - Button("설정") { - if let url = URL(string: UIApplication.openSettingsURLString) { - if UIApplication.shared.canOpenURL(url) { - UIApplication.shared.open(url, options: [:], completionHandler: nil) + + WeightHistoryView() + .environment(viewModel) + + if let pedometer = viewModel.pedometerData { + ActivityTable( + currentSteps: pedometer.steps, + currentDistance: pedometer.distance, + targetDistance: userData.targetDistance + ) + } + else { + ActivityTable(currentSteps: 10, currentDistance: 10, targetDistance: 20) + .overlay { + RoundedRectangle(cornerRadius: 21) + .fill(.ultraThinMaterial) + .foregroundStyle(.white) + .opacity(0.98) + .padding() + .overlay { + VStack { + Text("건강 앱 권한이 필요합니다.") + .font(.pretendardBold16) + .foregroundStyle(.text) + Button("설정") { + if let url = URL(string: UIApplication.openSettingsURLString) { + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } } } } } - } - } + } + } + } + .sheet(isPresented: $isTapModify, content: { + NavigationView { + WeightChangeView(viewModel: viewModel) + } + }) + .background(Color.backGround) + .onAppear { + viewModel.fetchPedometer() } } } - .sheet(isPresented: $isTapModify, content: { - NavigationView { - WeightChangeView(viewModel: viewModel) + else { + VStack { + Text("Please Wait...") } - }) - .background(Color.backGround) - .onAppear { - viewModel.fetchPedometer() } + + } } From dee6016afc3addb15ae0d2acb6e5c40450583943 Mon Sep 17 00:00:00 2001 From: JungHm Date: Tue, 3 Jun 2025 21:50:00 +0900 Subject: [PATCH 08/10] =?UTF-8?q?[FIX]=20#39=20RC=20=EC=A4=91=EC=B2=A9=20?= =?UTF-8?q?=EC=9D=B4=EC=8A=88=20=EC=88=98=EC=A0=95,=20=EC=9D=B4=EC=99=B8?= =?UTF-8?q?=20=EC=9E=91=EC=9D=80=20=EC=98=A4=EB=A5=98=EB=93=A4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Home/HomeViewModel.swift | 52 +++++++++++-------- .../Presentation/Home/View/HomeView.swift | 2 +- .../View/SubViews/WeightHistoryView.swift | 5 +- .../Home/View/WeightChangeView.swift | 4 +- .../ViewModel/OnboardingViewModel.swift | 40 +++++++------- 5 files changed, 51 insertions(+), 52 deletions(-) diff --git a/Dietto/Dietto/Presentation/Home/HomeViewModel.swift b/Dietto/Dietto/Presentation/Home/HomeViewModel.swift index 5405a64..df7fc7a 100644 --- a/Dietto/Dietto/Presentation/Home/HomeViewModel.swift +++ b/Dietto/Dietto/Presentation/Home/HomeViewModel.swift @@ -27,14 +27,15 @@ enum ChartTimeType: String, CaseIterable { @Observable final class HomeViewModel { - var isAnimating: Bool = false var chartTimeType: ChartTimeType = .weekly var bodyScaleHistory: [WeightEntity] = [] var pedometerData: PedometerModel? var currentDistance: Float { get { - if let distance = pedometerData?.distance { - return distance <= Float(userData.targetDistance) ? distance : Float(userData.targetDistance) + if let distance = pedometerData?.distance, + let targetDistance = userData?.targetDistance + { + return distance < Float(targetDistance) ? distance : Float(targetDistance) } else { return 0 } } @@ -59,28 +60,33 @@ final class HomeViewModel { self.weightHistroyUsecase = weightHistroyUsecase self.userStorageUsecase = userStorageUsecase - Task { - do { - let userData = try await userStorageUsecase.getUserData() - await MainActor.run { self.userData = userData } - } - catch { -#warning("여기에 에러핸들링 토스트 팝업 등 넣기 (데이터 없으면 온보딩으로 or fatal..?") - } - } +// getUserData() - bodyScaleHistoryFetch(type: chartTimeType) self.userStorageUsecase.changeEvent - .receive(on: DispatchQueue.main) .sink {[weak self] in - if let data = self?.userStorageUsecase.getUserData() { - self?.userData = data - } + self?.getUserData() + } .store(in: &bag) } + private func getUserData() { + Task { [weak self] in + do { + let userData = try await self?.userStorageUsecase.getUserData() + await MainActor.run { [weak self] in + self?.userData = userData + self?.bodyScaleHistoryFetch(type: self?.chartTimeType ?? .weekly) + #warning("이벤트 받았을때 차트 업데이트 되게 만들어야함.") + } + } + catch { +#warning("여기에 에러핸들링 토스트 팝업 등 넣기 (데이터 없으면 온보딩으로 or fatal..?") + } + } + } + func fetchPedometer() { // guard bag.isEmpty else { return } pedometerUsecase.startLivePedometerData() @@ -105,7 +111,7 @@ final class HomeViewModel { } Task { do { - try await userStorageUsecase.updateCurrentWeight(id: userData.id, currentWeight: value) + try await userStorageUsecase.updateCurrentWeight(id: id, currentWeight: value) if compareDate(Date(), lastModifiedDate) { try await weightHistroyUsecase.updateWeightByDate(weight: value, date: lastModifiedDate) } @@ -145,15 +151,15 @@ final class HomeViewModel { private func chartAnimate() { guard !bodyScaleHistory.isEmpty else { return } - isAnimating = true + for (index, _) in bodyScaleHistory.enumerated() { let delay = Double(index) * 0.05 - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in withAnimation(.bouncy) { - self.bodyScaleHistory[index].isAnimated = true + self?.bodyScaleHistory[index].isAnimated = true } - if index >= self.bodyScaleHistory.count - 1 { - self.isAnimating = false + if let count = self?.bodyScaleHistory.count, + index >= count - 1 { } } } diff --git a/Dietto/Dietto/Presentation/Home/View/HomeView.swift b/Dietto/Dietto/Presentation/Home/View/HomeView.swift index a35c411..9daca30 100644 --- a/Dietto/Dietto/Presentation/Home/View/HomeView.swift +++ b/Dietto/Dietto/Presentation/Home/View/HomeView.swift @@ -29,7 +29,7 @@ struct HomeView: View { if let pedometer = viewModel.pedometerData { ActivityTable( currentSteps: pedometer.steps, - currentDistance: pedometer.distance, + currentDistance: viewModel.currentDistance, targetDistance: userData.targetDistance ) } diff --git a/Dietto/Dietto/Presentation/Home/View/SubViews/WeightHistoryView.swift b/Dietto/Dietto/Presentation/Home/View/SubViews/WeightHistoryView.swift index ad6346f..ee98b7f 100644 --- a/Dietto/Dietto/Presentation/Home/View/SubViews/WeightHistoryView.swift +++ b/Dietto/Dietto/Presentation/Home/View/SubViews/WeightHistoryView.swift @@ -38,10 +38,7 @@ struct WeightHistoryView: View { Menu { ForEach(ChartTimeType.allCases, id: \.self) { type in Button(type.rawValue) { - if !viewModel.isAnimating { - viewModel.bodyScaleHistoryFetch(type: type) -// viewModel.chartAnimate() - } + viewModel.bodyScaleHistoryFetch(type: type) } } } label: { diff --git a/Dietto/Dietto/Presentation/Home/View/WeightChangeView.swift b/Dietto/Dietto/Presentation/Home/View/WeightChangeView.swift index d69a5dd..30800d3 100644 --- a/Dietto/Dietto/Presentation/Home/View/WeightChangeView.swift +++ b/Dietto/Dietto/Presentation/Home/View/WeightChangeView.swift @@ -24,8 +24,8 @@ struct WeightChangeView: View { Spacer() HStack(spacing: 2) { - ForEach(value, id: \.self) { item in - Text(item) + ForEach(value.indices, id: \.self) { index in + Text(value[index]) .contentTransition(.interpolate) .transition( .asymmetric(insertion: .push(from: .bottom), removal: .push(from: .top)) diff --git a/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift b/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift index 8412e3e..1dc7ea3 100644 --- a/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift +++ b/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift @@ -44,37 +44,33 @@ final class OnboardingViewModel: ObservableObject { self.weightHistroyUsecase = weightHistroyUsecase self.userStorageUsecase = userStorageUsecase +// getUserData() + + self.userStorageUsecase.changeEvent + .sink {[weak self] in + self?.getUserData() + } + .store(in: &bag) + } + + private func getUserData() { Task { do { - let user = try await userStorageUsecase.getUserData() + let user = try await self.userStorageUsecase.getUserData() await MainActor.run { - currentUserId = user.id - name = user.name - gender = user.gender - height = String(user.height) - weight = String(user.currentWeight) - targetWeight = user.targetWeight - targetDistance = user.targetDistance + self.currentUserId = user.id + self.name = user.name + self.gender = user.gender + self.height = String(user.height) + self.weight = String(user.currentWeight) + self.targetWeight = user.targetWeight + self.targetDistance = user.targetDistance } } catch { #warning("여기에 에러핸들링 토스트 팝업 등 넣기") } } - self.userStorageUsecase.changeEvent - .receive(on: DispatchQueue.main) - .sink {[weak self] in - if let user = self?.userStorageUsecase.getUserData() { - self?.currentUserId = user.id - self?.name = user.name - self?.gender = user.gender - self?.height = String(user.height) - self?.weight = String(user.currentWeight) - self?.targetWeight = user.targetWeight - self?.targetDistance = user.targetDistance - } - } - .store(in: &bag) } //MARK: - 프로필 설정 From 5bb987f69b0a83310a7d20e54de169515f7ec3c8 Mon Sep 17 00:00:00 2001 From: JungHm Date: Wed, 4 Jun 2025 09:58:08 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[FIX]=20#39=20Delete=EB=A7=8C=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=ED=95=98=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95..=20ModelContext=EB=A5=BC=20=EB=B0=B1=EA=B7=B8?= =?UTF-8?q?=EB=9D=BC=EC=9A=B4=EB=93=9C=EB=A1=9C=20=EC=A3=BC=EC=9E=85?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dietto/Dietto/Apps/DIContainer.swift | 13 ++++++++++--- .../Article/ViewModel/ArticleViewModel.swift | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Dietto/Dietto/Apps/DIContainer.swift b/Dietto/Dietto/Apps/DIContainer.swift index 0f42cce..8f2e737 100644 --- a/Dietto/Dietto/Apps/DIContainer.swift +++ b/Dietto/Dietto/Apps/DIContainer.swift @@ -10,21 +10,28 @@ import Observation @Observable final class DIContainer { private let alanUsecase: AlanUsecase - private var interestsUsecase: InterestsUsecase private let pedometerUsecase: PedometerUsecase - private let userStorageUsecase: UserStorageUsecase - private let weightHistoryUsecase: WeightHistoryUsecase + private var interestsUsecase: InterestsUsecase + private var userStorageUsecase: UserStorageUsecase + private var weightHistoryUsecase: WeightHistoryUsecase 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()) +// Task.detached(priority: .background) { [weak self] in +// self?.interestsUsecase = InterestsUsecaseImpl(repository: AnotherStorageRepositoryImpl()) +// self?.userStorageUsecase = UserStorageUsecaseImpl(storage: AnotherStorageRepositoryImpl()) +// self?.weightHistoryUsecase = WeightHistoryUsecaseImpl(repository: AnotherStorageRepositoryImpl()) +// } } diff --git a/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift b/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift index 4dcaa1b..4d6215e 100644 --- a/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift +++ b/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift @@ -76,7 +76,7 @@ final class ArticleViewModel: ObservableObject { } else { addInterest(title) Task { - do { try await storageUsecase.deleteInterests(InterestEntity(title: title)) } + do { try await storageUsecase.insertInterests(InterestEntity(title: title)) } catch { } #warning("여기에 에러핸들링 토스트 팝업 등 넣기") } From 4eb0723c65793f8a11cfc1903fb32812ae8aab97 Mon Sep 17 00:00:00 2001 From: JungHm Date: Wed, 4 Jun 2025 14:32:25 +0900 Subject: [PATCH 10/10] =?UTF-8?q?[FIX]=20#39=20weight=20history=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=EC=8B=9C=20=EC=B0=A8?= =?UTF-8?q?=ED=8A=B8=EC=97=90=20=EC=A0=81=EC=9A=A9=20=EC=95=88=EB=90=98?= =?UTF-8?q?=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dietto/Dietto/Extensions/Date+.swift | 7 +++++++ .../Dietto/Presentation/Home/HomeViewModel.swift | 16 ++-------------- .../ViewModel/OnboardingViewModel.swift | 9 +++++++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Dietto/Dietto/Extensions/Date+.swift b/Dietto/Dietto/Extensions/Date+.swift index 7e8891e..0921d9b 100644 --- a/Dietto/Dietto/Extensions/Date+.swift +++ b/Dietto/Dietto/Extensions/Date+.swift @@ -14,4 +14,11 @@ extension Date { formatter.dateFormat = "M월 d일" return formatter.string(from: self) } + + func isSameDateWithoutTime(date: Date) -> Bool { + let date1 = Calendar.current.dateComponents([.year, .month, .day], from: self) + let date2 = Calendar.current.dateComponents([.year, .month, .day], from: date) + + return date1.year == date2.year && date1.month == date2.month && date1.day == date2.day + } } diff --git a/Dietto/Dietto/Presentation/Home/HomeViewModel.swift b/Dietto/Dietto/Presentation/Home/HomeViewModel.swift index df7fc7a..eaede72 100644 --- a/Dietto/Dietto/Presentation/Home/HomeViewModel.swift +++ b/Dietto/Dietto/Presentation/Home/HomeViewModel.swift @@ -60,13 +60,10 @@ final class HomeViewModel { self.weightHistroyUsecase = weightHistroyUsecase self.userStorageUsecase = userStorageUsecase -// getUserData() - - self.userStorageUsecase.changeEvent .sink {[weak self] in self?.getUserData() - + self?.bodyScaleHistoryFetch(type: self?.chartTimeType ?? .weekly) } .store(in: &bag) } @@ -77,8 +74,6 @@ final class HomeViewModel { let userData = try await self?.userStorageUsecase.getUserData() await MainActor.run { [weak self] in self?.userData = userData - self?.bodyScaleHistoryFetch(type: self?.chartTimeType ?? .weekly) - #warning("이벤트 받았을때 차트 업데이트 되게 만들어야함.") } } catch { @@ -112,7 +107,7 @@ final class HomeViewModel { Task { do { try await userStorageUsecase.updateCurrentWeight(id: id, currentWeight: value) - if compareDate(Date(), lastModifiedDate) { + if lastModifiedDate.isSameDateWithoutTime(date: Date()) { try await weightHistroyUsecase.updateWeightByDate(weight: value, date: lastModifiedDate) } else { @@ -164,11 +159,4 @@ final class HomeViewModel { } } } - - private func compareDate(_ date1: Date, _ date2: Date) -> Bool { - let date1 = Calendar.current.dateComponents([.year, .month, .day], from: date1) - let date2 = Calendar.current.dateComponents([.year, .month, .day], from: date2) - - return date1.year == date2.year && date1.month == date2.month && date1.day == date2.day - } } diff --git a/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift b/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift index 1dc7ea3..bd6f053 100644 --- a/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift +++ b/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift @@ -44,8 +44,6 @@ final class OnboardingViewModel: ObservableObject { self.weightHistroyUsecase = weightHistroyUsecase self.userStorageUsecase = userStorageUsecase -// getUserData() - self.userStorageUsecase.changeEvent .sink {[weak self] in self?.getUserData() @@ -97,9 +95,16 @@ final class OnboardingViewModel: ObservableObject { private func editProfile(currentUserId: UUID, weight: Int, height: Int) { Task { do { + if let lastModifiedDate = try await weightHistroyUsecase.getWeightHistory(chartRange: .weekly).last?.date, + lastModifiedDate.isSameDateWithoutTime(date: Date()) + { + try await weightHistroyUsecase.updateWeightByDate(weight: weight, date: lastModifiedDate) + } + try await userStorageUsecase.updateUserDefaultData(id: currentUserId, name: name, gender: gender, height: height) try await userStorageUsecase.updateGoal(id: currentUserId, weight: targetWeight, distance: targetDistance) try await userStorageUsecase.updateCurrentWeight(id: currentUserId, currentWeight: weight) + await MainActor.run { isEditActive = false } } catch {