diff --git a/Dietto/Dietto/Apps/DIContainer.swift b/Dietto/Dietto/Apps/DIContainer.swift index 4b75d1a..8f2e737 100644 --- a/Dietto/Dietto/Apps/DIContainer.swift +++ b/Dietto/Dietto/Apps/DIContainer.swift @@ -10,17 +10,29 @@ import Observation @Observable final class DIContainer { private let alanUsecase: AlanUsecase - private let 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: 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()) +// } + } func getHomeViewModel() -> HomeViewModel { diff --git a/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift b/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift new file mode 100644 index 0000000..34226bb --- /dev/null +++ b/Dietto/Dietto/Data/Repository/AnotherStorageRepository.swift @@ -0,0 +1,77 @@ +// +// AnotherStorageRepository.swift +// Dietto +// +// Created by 안정흠 on 5/31/25. +// + + +// +// AnotherStorageRepository.swift +// Dietto +// +// Created by 안정흠 on 5/31/25. +// + +import Foundation +import SwiftData + +enum StorageError: String, Error { + case insertError + case updateError + case fetchError + case deleteError +} + +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 { + + 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) + try modelContext.save() + } + + func updateData(predicate: Predicate, updateBlock: @escaping (T) -> Void) async throws { + let descriptor = FetchDescriptor(predicate: predicate) + 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 { + modelContext.delete(item) + } + try modelContext.save() + } +} diff --git a/Dietto/Dietto/Domain/Usecases/InterestsUsecase.swift b/Dietto/Dietto/Domain/Usecases/InterestsUsecase.swift index 95d5986..d5f0b90 100644 --- a/Dietto/Dietto/Domain/Usecases/InterestsUsecase.swift +++ b/Dietto/Dietto/Domain/Usecases/InterestsUsecase.swift @@ -8,42 +8,44 @@ 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 { + do { try await repository.insertData(data: InterestsDTO(title: interests.title)) } + catch { + print(#function, error.localizedDescription) + throw StorageError.insertError + } } - func deleteInterests(_ interests: InterestEntity) { - do { - let title = interests.title - let predicate = #Predicate { $0.title == title } - try repository.deleteData(where: predicate) - } + func deleteInterests(_ interests: InterestEntity) async throws { + 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() -> [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 { - 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 58fb1a0..a29d11a 100644 --- a/Dietto/Dietto/Domain/Usecases/UserStorageUsecase.swift +++ b/Dietto/Dietto/Domain/Usecases/UserStorageUsecase.swift @@ -10,16 +10,16 @@ import Combine protocol UserStorageUsecase { var changeEvent: CurrentValueSubject { get } - - 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 var changeEvent: CurrentValueSubject = .init(()) @@ -27,7 +27,7 @@ final class UserStorageUsecaseImpl: UserStorageUs self.storage = storage } - func createUserData(_ user: UserEntity) { + func createUserData(_ user: UserEntity) async throws { let data = UserDTO( id: user.id, name: user.name, @@ -38,13 +38,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, @@ -56,16 +60,17 @@ 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 @@ -73,46 +78,45 @@ final class UserStorageUsecaseImpl: UserStorageUs changeEvent.send() } 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 } changeEvent.send() } 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 } changeEvent.send() } 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 7d371e8..5acd665 100644 --- a/Dietto/Dietto/Domain/Usecases/WeightHistoryUsecase.swift +++ b/Dietto/Dietto/Domain/Usecases/WeightHistoryUsecase.swift @@ -8,39 +8,42 @@ import Foundation protocol WeightHistoryUsecase { - func addNewWeight(weight: Int, date: Date) - func getWeightHistory(chartRange: ChartTimeType) -> [WeightEntity] - func updateWeightByDate(weight: Int, date: Date) - func deleteAllWeightHistory() + func addNewWeight(weight: Int, date: Date) async throws + func getWeightHistory(chartRange: ChartTimeType) async throws -> [WeightEntity] + func deleteAllWeightHistory() async throws + func updateWeightByDate(weight: Int, date: Date) 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 updateWeightByDate(weight: Int, date: Date) { + + func updateWeightByDate(weight: Int, date: Date) async throws { do { let predicate = #Predicate { $0.date == date } - try storage.updateData(predicate: predicate) { dto in + try await storage.updateData(predicate: predicate) { dto in dto.scale = weight } } @@ -49,14 +52,14 @@ final class WeightHistoryUsecaseImpl: WeightHisto } } - 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 } } + private func getDateRange(range: ChartTimeType) -> Predicate { let now = Date() 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/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..4d6215e 100644 --- a/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift +++ b/Dietto/Dietto/Presentation/Article/ViewModel/ArticleViewModel.swift @@ -22,11 +22,20 @@ 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 { + do { + let result = try await storageUsecase.fetchInterests() + await MainActor.run { self.selectedInterests = result } + } + catch { +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") + } + } } // MARK: - 아티클 로드 @@ -37,20 +46,20 @@ final class ArticleViewModel: ObservableObject { await MainActor.run{ articles = result } } catch { - +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") } } } // 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 +68,19 @@ final class ArticleViewModel: ObservableObject { func toggleInterest(_ title: String) { if selectedInterests.contains(where: { $0.title == title }) { removeInterest(title) - storageUsecase.deleteInterests(InterestEntity(title: title)) + Task { + do { try await storageUsecase.deleteInterests(InterestEntity(title: title))} + catch { } +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") + } } else { addInterest(title) - storageUsecase.insertInterests(InterestEntity(title: title)) + Task { + do { try await storageUsecase.insertInterests(InterestEntity(title: title)) } + catch { } +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") + } + } - print(selectedInterests) } } diff --git a/Dietto/Dietto/Presentation/Home/HomeViewModel.swift b/Dietto/Dietto/Presentation/Home/HomeViewModel.swift index 0026dc3..eaede72 100644 --- a/Dietto/Dietto/Presentation/Home/HomeViewModel.swift +++ b/Dietto/Dietto/Presentation/Home/HomeViewModel.swift @@ -27,20 +27,21 @@ 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 } } } - var userData: UserEntity + var userData: UserEntity? private let pedometerUsecase: PedometerUsecase @@ -51,29 +52,36 @@ 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 - } - else { fatalError("데이터 없음") } - 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() + self?.bodyScaleHistoryFetch(type: self?.chartTimeType ?? .weekly) } .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 + } + } + catch { +#warning("여기에 에러핸들링 토스트 팝업 등 넣기 (데이터 없으면 온보딩으로 or fatal..?") + } + } + } + func fetchPedometer() { // guard bag.isEmpty else { return } pedometerUsecase.startLivePedometerData() @@ -91,85 +99,64 @@ final class HomeViewModel { func updateCurrentBodyScale(_ value: String) { guard let value = Int(value), + let id = userData?.id, let lastModifiedDate = bodyScaleHistory.last?.date else { print("\(#function) : FAILED to update current body scale") return } - userStorageUsecase.updateCurrentWeight(id: userData.id, currentWeight: value) - if compareDate(Date(), lastModifiedDate) { - weightHistroyUsecase.updateWeightByDate(weight: value, date: lastModifiedDate) - } - else { - weightHistroyUsecase.addNewWeight(weight: value, date: Date()) + Task { + do { + try await userStorageUsecase.updateCurrentWeight(id: id, currentWeight: value) + if lastModifiedDate.isSameDateWithoutTime(date: Date()) { + try await weightHistroyUsecase.updateWeightByDate(weight: value, date: lastModifiedDate) + } + else { + try await weightHistroyUsecase.addNewWeight(weight: value, date: Date()) + } + await MainActor.run { + userData?.currentWeight = value + } + bodyScaleHistoryFetch(type: chartTimeType) + } + catch { +#warning("여기에 에러핸들링 토스트 팝업 등 넣기") + } } - 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() { 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 { } } } } - - 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 - } } - - -// 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 dd1eefc..9daca30 100644 --- a/Dietto/Dietto/Presentation/Home/View/HomeView.swift +++ b/Dietto/Dietto/Presentation/Home/View/HomeView.swift @@ -12,66 +12,75 @@ struct HomeView: View { @State var viewModel: HomeViewModel @State var isTapModify: Bool = false var body: some View { - VStack(spacing: 0) { - HomeHeader() - ScrollView { - WeightTable( - startWeight: $viewModel.userData.startWeight, - targetWeight: $viewModel.userData.targetWeight, - currentWeight: $viewModel.userData.currentWeight, - isTapModify: $isTapModify - ) - - WeightHistoryView() - .environment(viewModel) - - if let pedometer = viewModel.pedometerData { - ActivityTable( - currentSteps: pedometer.steps, - currentDistance: viewModel.currentDistance, - targetDistance: viewModel.userData.targetDistance + if let userData = viewModel.userData { + VStack(spacing: 0) { + HomeHeader() + ScrollView { + WeightTable( + startWeight: userData.startWeight, + targetWeight: userData.targetWeight, + currentWeight: userData.currentWeight, + isTapModify: $isTapModify ) - } - 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: viewModel.currentDistance, + 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() } + + } } -#Preview { - HomeView(viewModel: HomeViewModel()) -} +//#Preview { +// HomeView(viewModel: HomeViewModel()) +//} 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/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/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/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 bd8b1a2..bd6f053 100644 --- a/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift +++ b/Dietto/Dietto/Presentation/Onboarding/ViewModel/OnboardingViewModel.swift @@ -14,8 +14,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 = "" @@ -38,39 +38,39 @@ final class OnboardingViewModel: ObservableObject { private var bag = Set() 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 - } - 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 - } + self?.getUserData() } .store(in: &bag) } + private func getUserData() { + Task { + do { + let user = try await self.userStorageUsecase.getUserData() + await MainActor.run { + 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("여기에 에러핸들링 토스트 팝업 등 넣기") + } + } + } + //MARK: - 프로필 설정 func saveProfile() { guard let weight = Int(weight), let height = Int(height) else { @@ -83,37 +83,65 @@ 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 { + 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 { +#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) { @@ -127,9 +155,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()) +//}