diff --git a/Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift b/Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift index 4aa59b1f..eae36cde 100644 --- a/Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift +++ b/Cherrish-iOS/Cherrish-iOS/Data/DataDependencyAssembler.swift @@ -15,7 +15,7 @@ final class DataDependencyAssembler: DependencyAssembler { self.networkService = DefaultNetworkService() self.userDefaultService = DefaultUserDefaultService() } - + func assemble() { DIContainer.shared.register(type: CalendarInterface.self) { return DefaultCalendarRepository( @@ -23,7 +23,7 @@ final class DataDependencyAssembler: DependencyAssembler { userDefaultService: self.userDefaultService ) } - + DIContainer.shared.register(type: HomeInterface.self) { return DefaultHomeRepository( networkService: self.networkService, @@ -41,6 +41,13 @@ final class DataDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: TreatmentInterface.self) { return DefaultTreatmentRepository(networkService: self.networkService, userDefaultService: self.userDefaultService) } + + DIContainer.shared.register(type: DemoInterface.self) { + return DefaultDemoRepository( + networkService: self.networkService, + userDefaultService: self.userDefaultService + ) + } DIContainer.shared.register(type: MyPageInterface.self) { return DefaultMyPageRepository( diff --git a/Cherrish-iOS/Cherrish-iOS/Data/Model/Demo/FetchChallengesResponseDTO.swift b/Cherrish-iOS/Cherrish-iOS/Data/Model/Demo/FetchChallengesResponseDTO.swift new file mode 100644 index 00000000..de5c7bfb --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Data/Model/Demo/FetchChallengesResponseDTO.swift @@ -0,0 +1,54 @@ +// +// FetchChallengesResponseDTO.swift +// Cherrish-iOS +// +// Created by 이나연 on 1/21/26. +// + +import Foundation + +struct FetchChallengesResponseDTO: Decodable { + let challengeId: Int + let title: String + let currentDay: Int + let progressPercentage: Int + let cherryLevel: Int + let cherryLevelName: String + let progressToNextLevel: Double + let remainingRoutinesToNextLevel: Int + let todayRoutines: [RoutineResponseDTO] + let cheeringMessage: String +} + +struct RoutineResponseDTO: Decodable { + let routineId: Int + let name: String + let scheduledDate: String + let isComplete: Bool +} + +extension FetchChallengesResponseDTO { + func toEntity() -> ChallengeEntity { + .init( + challengeID: challengeId, + title: title, + currentDay: currentDay, + progressPercentage: progressPercentage, + cherryLevel: cherryLevel, + cherryLevelName: cherryLevelName, + progressToNextLevel: progressToNextLevel, + remainingRoutinesToNextLevel: remainingRoutinesToNextLevel, + todayRoutines: todayRoutines.map { $0.toEntity() } + ) + } +} + +extension RoutineResponseDTO { + func toEntity() -> RoutineEntity { + .init( + routineID: routineId, + name: name, + isComplete: isComplete + ) + } +} diff --git a/Cherrish-iOS/Cherrish-iOS/Data/Model/RoutineToggleResponseDTO.swift b/Cherrish-iOS/Cherrish-iOS/Data/Model/RoutineToggleResponseDTO.swift new file mode 100644 index 00000000..0e68005f --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Data/Model/RoutineToggleResponseDTO.swift @@ -0,0 +1,24 @@ +// +// RoutineToggleResponseDTO.swift +// Cherrish-iOS +// +// Created by 이나연 on 1/21/26. +// + +import Foundation + +struct RoutineToggleResponseDTO: Decodable { + let routineId: Int + let name: String + let isComplete: Bool +} + +extension RoutineToggleResponseDTO { + func toEntity() -> RoutineEntity { + .init( + routineID: routineId, + name: name, + isComplete: isComplete + ) + } +} diff --git a/Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/DemoAPI.swift b/Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/DemoAPI.swift new file mode 100644 index 00000000..10240d31 --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Data/Network/EndPoint/DemoAPI.swift @@ -0,0 +1,72 @@ +// +// DemoAPI.swift +// Cherrish-iOS +// +// Created by 이나연 on 1/21/26. +// + +import Foundation + +import Alamofire + +enum DemoAPI { + case fetchChallenges(userID: Int) + case advance(userID: Int) + case routineToggle(userID: Int, routineID: Int) +} + +extension DemoAPI: EndPoint { + var basePath: String { + "/api/demo/challenges" + } + + var path: String { + switch self { + case .fetchChallenges: + return "" + case .advance: + return "/advance-day" + case .routineToggle(_, let routineID): + return "/routines/\(routineID)/toggle" + } + } + + var method: Alamofire.HTTPMethod { + switch self { + case .fetchChallenges: + return .get + case .advance: + return .post + case .routineToggle(_, let routineID): + return .patch + } + } + + var headers: HeaderType { + switch self { + case .fetchChallenges(let userID), + .advance(let userID), + .routineToggle(let userID, _): + return .withAuth(userID: userID) + } + } + + var parameterEncoding: any Alamofire.ParameterEncoding { + switch self { + case .fetchChallenges: + return URLEncoding.default + case .advance, .routineToggle: + return JSONEncoding.default + } + } + + var queryParameters: [String : Any]? { + return nil + } + + var bodyParameters: Alamofire.Parameters? { + return nil + } + + +} diff --git a/Cherrish-iOS/Cherrish-iOS/Data/Repository/DemoRepository.swift b/Cherrish-iOS/Cherrish-iOS/Data/Repository/DemoRepository.swift new file mode 100644 index 00000000..cbea6dba --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Data/Repository/DemoRepository.swift @@ -0,0 +1,49 @@ +// +// DemoRepository.swift +// Cherrish-iOS +// +// Created by 이나연 on 1/21/26. +// + +import Foundation + +struct DefaultDemoRepository: DemoInterface { + private let networkService: NetworkService + private let userDefaultService: UserDefaultService + + init( + networkService: NetworkService, + userDefaultService: UserDefaultService + ) { + self.networkService = networkService + self.userDefaultService = userDefaultService + } + + func fetchChallenges() async throws -> ChallengeEntity { + let userID: Int = userDefaultService.load(key: .userID) ?? 1 + let response = try await networkService.request( + DemoAPI.fetchChallenges(userID: userID), + decodingType: FetchChallengesResponseDTO.self + ) + + return response.toEntity() + } + + func advance() async throws -> ChallengeEntity { + let userID: Int = userDefaultService.load(key: .userID) ?? 1 + let response = try await networkService.request( + DemoAPI.advance(userID: userID), + decodingType: FetchChallengesResponseDTO.self + ) + return response.toEntity() + } + + func toggleRoutine(routineID: Int) async throws -> RoutineEntity { + let userID: Int = userDefaultService.load(key: .userID) ?? 1 + let response = try await networkService.request( + DemoAPI.routineToggle(userID: userID, routineID: routineID), + decodingType: RoutineToggleResponseDTO.self + ) + return response.toEntity() + } +} diff --git a/Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift b/Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift index f8160306..3c4956ec 100644 --- a/Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift +++ b/Cherrish-iOS/Cherrish-iOS/Domain/DomainDependencyAssembler.swift @@ -60,10 +60,26 @@ final class DomainDependencyAssembler: DependencyAssembler { guard let treatmentRepository = DIContainer.shared.resolve(type: TreatmentInterface.self) else { return } - + DIContainer.shared.register(type: FetchTreatmentCategoriesUseCase.self) { return DefaultFetchTreatmentCategoriesUseCase(repository: treatmentRepository) } + + guard let demoRepository = DIContainer.shared.resolve(type: DemoInterface.self) else { + return + } + + DIContainer.shared.register(type: FetchChallengeUseCase.self) { + return DefaultFetchChallengeUseCase(repository: demoRepository) + } + + DIContainer.shared.register(type: ToggleRoutineUseCase.self) { + return DefaultToggleRoutineUseCase(repository: demoRepository) + } + + DIContainer.shared.register(type: AdvanceDayUseCase.self) { + return DefaultAdvanceDayUseCase(repository: demoRepository) + } DIContainer.shared.register(type: FetchTreatmentsUseCase.self) { return DefaultFetchTreatmentsUseCase(repository: treatmentRepository) diff --git a/Cherrish-iOS/Cherrish-iOS/Domain/Interface/DemoInterface.swift b/Cherrish-iOS/Cherrish-iOS/Domain/Interface/DemoInterface.swift new file mode 100644 index 00000000..8ab55c53 --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Domain/Interface/DemoInterface.swift @@ -0,0 +1,14 @@ +// +// DemoInterface.swift +// Cherrish-iOS +// +// Created by 이나연 on 1/21/26. +// + +import Foundation + +protocol DemoInterface { + func fetchChallenges() async throws -> ChallengeEntity + func advance() async throws -> ChallengeEntity + func toggleRoutine(routineID: Int) async throws -> RoutineEntity +} diff --git a/Cherrish-iOS/Cherrish-iOS/Domain/Model/Demo/ChallengeEntity.swift b/Cherrish-iOS/Cherrish-iOS/Domain/Model/Demo/ChallengeEntity.swift new file mode 100644 index 00000000..df5ed05d --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Domain/Model/Demo/ChallengeEntity.swift @@ -0,0 +1,20 @@ +// +// ChallengeEntity.swift +// Cherrish-iOS +// +// Created by 이나연 on 1/21/26. +// + +import Foundation + +struct ChallengeEntity { + let challengeID: Int + let title: String + let currentDay: Int + let progressPercentage: Int + let cherryLevel: Int + let cherryLevelName: String + let progressToNextLevel: Double + let remainingRoutinesToNextLevel: Int + let todayRoutines: [RoutineEntity] +} diff --git a/Cherrish-iOS/Cherrish-iOS/Domain/Model/Demo/RoutineEntity.swift b/Cherrish-iOS/Cherrish-iOS/Domain/Model/Demo/RoutineEntity.swift new file mode 100644 index 00000000..d90ba395 --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Domain/Model/Demo/RoutineEntity.swift @@ -0,0 +1,14 @@ +// +// RoutineEntity.swift +// Cherrish-iOS +// +// Created by 이나연 on 1/21/26. +// + +import Foundation + +struct RoutineEntity { + let routineID: Int + let name: String + let isComplete: Bool +} diff --git a/Cherrish-iOS/Cherrish-iOS/Domain/UseCase/AdvanceDayUseCase.swift b/Cherrish-iOS/Cherrish-iOS/Domain/UseCase/AdvanceDayUseCase.swift new file mode 100644 index 00000000..968a60be --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Domain/UseCase/AdvanceDayUseCase.swift @@ -0,0 +1,24 @@ +// +// AdvanceDayUseCase.swift +// Cherrish-iOS +// +// Created by 송성용 on 1/21/26. +// + +import Foundation + +protocol AdvanceDayUseCase { + func execute() async throws -> ChallengeEntity +} + +struct DefaultAdvanceDayUseCase: AdvanceDayUseCase { + private let repository: DemoInterface + + init(repository: DemoInterface) { + self.repository = repository + } + + func execute() async throws -> ChallengeEntity { + try await repository.advance() + } +} diff --git a/Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchChallengeUseCase.swift b/Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchChallengeUseCase.swift new file mode 100644 index 00000000..ec08f662 --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Domain/UseCase/FetchChallengeUseCase.swift @@ -0,0 +1,24 @@ +// +// FetchChallengeUseCase.swift +// Cherrish-iOS +// +// Created by 송성용 on 1/21/26. +// + +import Foundation + +protocol FetchChallengeUseCase { + func execute() async throws -> ChallengeEntity +} + +struct DefaultFetchChallengeUseCase: FetchChallengeUseCase { + private let repository: DemoInterface + + init(repository: DemoInterface) { + self.repository = repository + } + + func execute() async throws -> ChallengeEntity { + try await repository.fetchChallenges() + } +} diff --git a/Cherrish-iOS/Cherrish-iOS/Domain/UseCase/ToggleRoutineUseCase.swift b/Cherrish-iOS/Cherrish-iOS/Domain/UseCase/ToggleRoutineUseCase.swift new file mode 100644 index 00000000..aa64147a --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Domain/UseCase/ToggleRoutineUseCase.swift @@ -0,0 +1,24 @@ +// +// ToggleRoutineUseCase.swift +// Cherrish-iOS +// +// Created by 송성용 on 1/21/26. +// + +import Foundation + +protocol ToggleRoutineUseCase { + func execute(routineID: Int) async throws -> RoutineEntity +} + +struct DefaultToggleRoutineUseCase: ToggleRoutineUseCase { + private let repository: DemoInterface + + init(repository: DemoInterface) { + self.repository = repository + } + + func execute(routineID: Int) async throws -> RoutineEntity { + try await repository.toggleRoutine(routineID: routineID) + } +} diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift index 0eeb760e..f6c7343b 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/ChallengeProgressView.swift @@ -12,9 +12,9 @@ enum CherryLevel: Int { case bbo case pang case ggu - + var levelNumber: Int { rawValue } - + static func from(progressRate: Double) -> CherryLevel { switch progressRate { case 0.0..<25.0: @@ -29,7 +29,7 @@ enum CherryLevel: Int { return .mong } } - + var name: String { switch self { case .mong: return "몽롱체리" @@ -38,7 +38,7 @@ enum CherryLevel: Int { case .ggu: return "꾸꾸체리" } } - + var cherryImage: Image { Image("cherry\(rawValue)") } @@ -48,32 +48,15 @@ enum CherryLevel: Int { } struct ChallengeProgressView: View { - - @State private var isChecked = false - private var progressRate: Double = 100 - private var cherryLevel: CherryLevel { - CherryLevel.from(progressRate: progressRate) - } - private var remainMissions: Int = 3 - - @State private var missions: [String] = [ - "진정 토너 + 세럼", - "진정 토너 + 세럼", - "선크림 3번 바르기", - "선크림 3번 바르기", - "선크림 3번 바르기", - "선크림 3번 바르기" - ] - - @State private var selectedStates: [Bool] = Array(repeating: false, count: 6) + @StateObject var viewModel: ChallengeProgressViewModel let buttonState: ButtonState = .active - + var body: some View { ScrollView { - VStack{ + VStack { HStack { - TypographyText("피부 컨디션 챌린지", style: .title1_sb_18, color: .gray1000) + TypographyText(viewModel.challengeTitle, style: .title1_sb_18, color: .gray1000) .padding(.trailing, 12.adjustedW) TypographyText("7일 플랜", style: .body3_m_12, color: .gray700) .padding(.horizontal, 8.adjustedW) @@ -91,20 +74,23 @@ struct ChallengeProgressView: View { .padding(.vertical, 24.adjustedH) } .scrollIndicators(.hidden) + .task { + await viewModel.loadChallenge() + } } } extension ChallengeProgressView { - private var CherryGrowthView : some View { + private var CherryGrowthView: some View { VStack { VStack { HStack { - TypographyText("Lv.\(cherryLevel.levelNumber) \(cherryLevel.name)", style: .body1_m_14, color: .gray900) + TypographyText("Lv.\(viewModel.cherryLevel.levelNumber) \(viewModel.cherryLevel.name)", style: .body1_m_14, color: .gray900) Spacer() } - cherryLevel.cherryImage + viewModel.cherryLevel.cherryImage .padding(.top, 14.adjustedH) - TypographyText("체리가 크려면 \(remainMissions)개의 미션을 수행해야 해요!", style: .body2_r_13, color: .gray800) + TypographyText("체리가 크려면 \(viewModel.remainMissions)개의 미션을 수행해야 해요!", style: .body2_r_13, color: .gray800) .padding(.top, 14.adjustedH) } .padding(.horizontal, 25.adjustedW) @@ -114,17 +100,16 @@ extension ChallengeProgressView { .padding(.vertical, 14.adjustedH) VStack { HStack { - TypographyText("챌린지 달성률 \(progressRate)%", style: .body1_m_14, color: .gray900) + TypographyText("챌린지 달성률 \(viewModel.progressRate)%", style: .body1_m_14, color: .gray900) Spacer() } .padding(.bottom, 12.adjustedH) - cherryLevel.progressImage + viewModel.cherryLevel.progressImage .padding(.bottom, 11.adjustedH) } .padding(.horizontal, 25.adjustedW) } .padding(.top, 16.adjustedH) - .background( RoundedRectangle(cornerRadius: 10) .fill( @@ -140,30 +125,40 @@ extension ChallengeProgressView { } extension ChallengeProgressView { - private var CherryTodoView : some View { + private var CherryTodoView: some View { VStack { HStack { - TypographyText("4일차 TO-DO 미션", style: .body1_sb_14, color: .gray1000) + TypographyText("\(viewModel.currentDay)일차 TO-DO 미션", style: .body1_sb_14, color: .gray1000) Spacer() } Spacer() VStack(spacing: 8.adjustedH) { - ForEach(missions.indices, id: \.self) { index in + ForEach(viewModel.todayRoutines, id: \.routineID) { routine in CheckBoxComponent( - text: missions[index], - isChecked: $selectedStates[index] + text: routine.name, + isChecked: Binding( + get: { routine.isComplete }, + set: { _ in + Task { + await viewModel.toggleRoutine(routineID: routine.routineID) + } + } + ) ) } } + CherrishButton( title: "오늘 미션 종료하기", type: .small, state: .constant(buttonState), leadingIcon: nil, trailingIcon: nil - ){ - + ) { + Task { + await viewModel.advanceDay() } + } .padding(.top, 10.adjustedH) .padding(.bottom, 18.adjustedH) } diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ViewModel/ChallengeProgressViewModel.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ViewModel/ChallengeProgressViewModel.swift new file mode 100644 index 00000000..c695a5d4 --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/ChallengeView/Coordinator/ViewModel/ChallengeProgressViewModel.swift @@ -0,0 +1,107 @@ +// +// ChallengeProgressViewModel.swift +// Cherrish-iOS +// +// Created by 이나연 on 1/21/26. +// + +import Foundation + +final class ChallengeProgressViewModel: ObservableObject { + @Published private(set) var challengeData: ChallengeEntity? + @Published private(set) var isLoading: Bool = false + @Published private(set) var errorMessage: String? + @Published private(set) var cherryLevel: CherryLevel = .mong + @Published private(set) var remainMissions: Int = 0 + @Published private(set) var progressRate = 0 + @Published private(set) var currentDay: Int = 1 + @Published private(set) var challengeTitle: String = "챌린지" + @Published private(set) var todayRoutines: [RoutineEntity] = [] + + private let fetchChallengeUseCase: FetchChallengeUseCase + private let toggleRoutineUseCase: ToggleRoutineUseCase + private let advanceDayUseCase: AdvanceDayUseCase + + init( + fetchChallengeUseCase: FetchChallengeUseCase, + toggleRoutineUseCase: ToggleRoutineUseCase, + advanceDayUseCase: AdvanceDayUseCase + ) { + self.fetchChallengeUseCase = fetchChallengeUseCase + self.toggleRoutineUseCase = toggleRoutineUseCase + self.advanceDayUseCase = advanceDayUseCase + } + + @MainActor + func loadChallenge() async { + isLoading = true + errorMessage = nil + + do { + challengeData = try await fetchChallengeUseCase.execute() + updateInfo() + } catch { + errorMessage = error.localizedDescription + } + isLoading = false + } + + @MainActor + func toggleRoutine(routineID: Int) async { + do { + let updatedRoutine = try await toggleRoutineUseCase.execute(routineID: routineID) + + guard let currentData = challengeData else { return } + + let updatedRoutines = currentData.todayRoutines.map { routine in + if routine.routineID == updatedRoutine.routineID { + return updatedRoutine + } + return routine + } + + challengeData = ChallengeEntity( + challengeID: currentData.challengeID, + title: currentData.title, + currentDay: currentData.currentDay, + progressPercentage: currentData.progressPercentage, + cherryLevel: currentData.cherryLevel, + cherryLevelName: currentData.cherryLevelName, + progressToNextLevel: currentData.progressToNextLevel, + remainingRoutinesToNextLevel: currentData.remainingRoutinesToNextLevel, + todayRoutines: updatedRoutines + ) + updateInfo() + } catch { + errorMessage = error.localizedDescription + } + } + + @MainActor + func advanceDay() async { + isLoading = true + errorMessage = nil + + do { + challengeData = try await advanceDayUseCase.execute() + } catch { + errorMessage = error.localizedDescription + } + + isLoading = false + updateInfo() + } +} + +extension ChallengeProgressViewModel { + @MainActor + private func updateInfo() { + guard let challengeData else { return } + cherryLevel = CherryLevel.from(progressRate: Double(challengeData.progressPercentage)) + remainMissions = challengeData.remainingRoutinesToNextLevel + progressRate = challengeData.progressPercentage + currentDay = challengeData.currentDay + challengeTitle = challengeData.title + todayRoutines = challengeData.todayRoutines + } +} diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift index 6c5b75a3..4dfa6931 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/PresentationDependencyAssembler.swift @@ -79,5 +79,21 @@ final class PresentationDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: MyPageViewModel.self) { return MyPageViewModel(fetchUserInfoUseCase: fetchUserInfoUseCase) } + + guard let fetchChallengeUseCase = DIContainer.shared.resolve(type: FetchChallengeUseCase.self), + let toggleRoutineUseCase = DIContainer.shared.resolve(type: ToggleRoutineUseCase.self), + let advanceDayUseCase = DIContainer.shared.resolve(type: AdvanceDayUseCase.self) + else { + CherrishLogger.error(CherrishError.DIFailedError) + return + } + + DIContainer.shared.register(type: ChallengeProgressViewModel.self) { + return ChallengeProgressViewModel( + fetchChallengeUseCase: fetchChallengeUseCase, + toggleRoutineUseCase: toggleRoutineUseCase, + advanceDayUseCase: advanceDayUseCase + ) + } } } diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift index ca56d8a8..7f95e4d6 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/ViewFactory.swift @@ -104,8 +104,11 @@ final class ViewFactory: ViewFactoryProtocol { func makeLoadingView() -> LoadingView { return LoadingView() } - + func makeChallengeProgressView() -> ChallengeProgressView { - return ChallengeProgressView() + guard let viewModel = DIContainer.shared.resolve(type: ChallengeProgressViewModel.self) else { + fatalError() + } + return ChallengeProgressView(viewModel: viewModel) } }