diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index 0e26a227..0054dd2e 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -59,6 +59,8 @@ CE4545CD295D7AF4003201E1 /* TaBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE4545CC295D7AF4003201E1 /* TaBarController.swift */; }; CE4545D2295D7AF5003201E1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE4545D1295D7AF5003201E1 /* Assets.xcassets */; }; CE4545D5295D7AF5003201E1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE4545D3295D7AF5003201E1 /* LaunchScreen.storyboard */; }; + CE4942AA296FCCFC00736701 /* UploadedCourseDetailRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE4942A9296FCCFC00736701 /* UploadedCourseDetailRouter.swift */; }; + CE4942AD296FCD2300736701 /* UploadedCourseDetailResponseDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE4942AC296FCD2300736701 /* UploadedCourseDetailResponseDto.swift */; }; CE55BC11296D4EA600E8CD69 /* RunningRecordRequestDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE55BC10296D4EA600E8CD69 /* RunningRecordRequestDto.swift */; }; CE5645162961B72E000A2856 /* ImageLiterals.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5645152961B72E000A2856 /* ImageLiterals.swift */; }; CE58759E29601476005D967E /* LoadingIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE58759D29601476005D967E /* LoadingIndicator.swift */; }; @@ -170,9 +172,7 @@ CE0D9FD229648DA300CEB5CD /* CustomAlertVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAlertVC.swift; sourceTree = ""; }; CE10063929680C5700FD31FB /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; CE10063D29680C8100FD31FB /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; - CE10063E29680C8800FD31FB /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; CE10064329680CB400FD31FB /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; - CE10064429680CBC00FD31FB /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; CE10064D29680D2500FD31FB /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; CE10064F29680D3300FD31FB /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; CE10065029680D3800FD31FB /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; @@ -211,6 +211,8 @@ CE4545D1295D7AF5003201E1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CE4545D4295D7AF5003201E1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; CE4545D6295D7AF5003201E1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CE4942A9296FCCFC00736701 /* UploadedCourseDetailRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadedCourseDetailRouter.swift; sourceTree = ""; }; + CE4942AC296FCD2300736701 /* UploadedCourseDetailResponseDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UploadedCourseDetailResponseDto.swift; sourceTree = ""; }; CE55BC10296D4EA600E8CD69 /* RunningRecordRequestDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunningRecordRequestDto.swift; sourceTree = ""; }; CE5645152961B72E000A2856 /* ImageLiterals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageLiterals.swift; sourceTree = ""; }; CE58759D29601476005D967E /* LoadingIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicator.swift; sourceTree = ""; }; @@ -285,7 +287,6 @@ CEEC6B432961C59F00D00E1E /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; CEEC6B442961C5A800D00E1E /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; CEEC6B452961C5B200D00E1E /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; - CEEC6B462961C5BB00D00E1E /* .gitkeep */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitkeep; sourceTree = ""; }; CEEC6B482961C5E200D00E1E /* SplashVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashVC.swift; sourceTree = ""; }; CEEC6B4A2961D89700D00E1E /* CustomNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationBar.swift; sourceTree = ""; }; CEF3CD97296D63B9002723A1 /* CourseStorageRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseStorageRouter.swift; sourceTree = ""; }; @@ -411,7 +412,7 @@ CE10063329680BFD00FD31FB /* CourseDetailDto */ = { isa = PBXGroup; children = ( - CE10063E29680C8800FD31FB /* .gitkeep */, + CE4942AB296FCD1000736701 /* ResponseDto */, ); path = CourseDetailDto; sourceTree = ""; @@ -687,7 +688,6 @@ CE17F0472961C3E900E1DED0 /* VC */ = { isa = PBXGroup; children = ( - CEEC6B462961C5BB00D00E1E /* .gitkeep */, A3BC2F422966A93100198261 /* CourseDetailVC.swift */, ); path = VC; @@ -765,7 +765,7 @@ CE40BB2A296808440030ABCA /* CourseDetailRouter */ = { isa = PBXGroup; children = ( - CE10064429680CBC00FD31FB /* .gitkeep */, + CE4942A9296FCCFC00736701 /* UploadedCourseDetailRouter.swift */, ); path = CourseDetailRouter; sourceTree = ""; @@ -808,6 +808,14 @@ path = "Runnect-iOS"; sourceTree = ""; }; + CE4942AB296FCD1000736701 /* ResponseDto */ = { + isa = PBXGroup; + children = ( + CE4942AC296FCD2300736701 /* UploadedCourseDetailResponseDto.swift */, + ); + path = ResponseDto; + sourceTree = ""; + }; CE55BC0F296D4E5500E8CD69 /* RNUtils */ = { isa = PBXGroup; children = ( @@ -1321,6 +1329,7 @@ CE14677829658C7200DCEA1B /* Stopwatch.swift in Sources */, CE40BB22296806140030ABCA /* NetworkHelper.swift in Sources */, CE5645162961B72E000A2856 /* ImageLiterals.swift in Sources */, + CE4942AD296FCD2300736701 /* UploadedCourseDetailResponseDto.swift in Sources */, CE6655CD295D856300C64E12 /* KeyPathFindable.swift in Sources */, CE29D582296402B500F47542 /* CourseDrawingVC.swift in Sources */, CE6655E4295D884600C64E12 /* UILabel+.swift in Sources */, @@ -1420,6 +1429,7 @@ CE17F02D2961BBA100E1DED0 /* ColorLiterals.swift in Sources */, CEC2A68E2962AF2C00160BF7 /* RNMarker.swift in Sources */, CE6655D2295D862A00C64E12 /* Publisher+Driver.swift in Sources */, + CE4942AA296FCCFC00736701 /* UploadedCourseDetailRouter.swift in Sources */, A3BC2F382963CE3700198261 /* ActivityRecordInfoModel.swift in Sources */, CE6655E6295D887F00C64E12 /* UIStackView+.swift in Sources */, A3BC2F34296303A600198261 /* GoalRewardInfoCVC.swift in Sources */, diff --git a/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDetailDto/.gitkeep b/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDetailDto/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDetailDto/ResponseDto/UploadedCourseDetailResponseDto.swift b/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDetailDto/ResponseDto/UploadedCourseDetailResponseDto.swift new file mode 100644 index 00000000..4f973bd2 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDetailDto/ResponseDto/UploadedCourseDetailResponseDto.swift @@ -0,0 +1,23 @@ +// +// UploadedCourseDetailResponseDto.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/12. +// + +import Foundation + +// MARK: - UploadedCourseDetailResponseDto + +struct UploadedCourseDetailResponseDto: Codable { + let user: UploadUser + let publicCourse: PublicCourse +} + +// MARK: - UploadUser + +struct UploadUser: Codable { + let nickname: String + let level: Int + let image: String +} diff --git a/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDiscoveryDto/ResponseDto/PickedMapListResponseDto.swift b/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDiscoveryDto/ResponseDto/PickedMapListResponseDto.swift index 7e23d96c..32bcf904 100644 --- a/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDiscoveryDto/ResponseDto/PickedMapListResponseDto.swift +++ b/Runnect-iOS/Runnect-iOS/Network/Dto/CourseDiscoveryDto/ResponseDto/PickedMapListResponseDto.swift @@ -20,6 +20,8 @@ struct PublicCourse: Codable { let title: String let image: String let scarp: Bool? + let description: String? + let distance: Float? let departure: CourseDiscoveryDeparture } @@ -28,4 +30,6 @@ struct PublicCourse: Codable { struct CourseDiscoveryDeparture: Codable { let region: String let city: String + let town: String? + let name: String? } diff --git a/Runnect-iOS/Runnect-iOS/Network/Router/CourseDetailRouter/.gitkeep b/Runnect-iOS/Runnect-iOS/Network/Router/CourseDetailRouter/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Runnect-iOS/Runnect-iOS/Network/Router/CourseDetailRouter/UploadedCourseDetailRouter.swift b/Runnect-iOS/Runnect-iOS/Network/Router/CourseDetailRouter/UploadedCourseDetailRouter.swift new file mode 100644 index 00000000..caf87e03 --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Network/Router/CourseDetailRouter/UploadedCourseDetailRouter.swift @@ -0,0 +1,52 @@ +// +// UploadedCourseDetailRouter.swift +// Runnect-iOS +// +// Created by sejin on 2023/01/12. +// + +import Foundation + +import Moya + +enum UploadedCourseDetailRouter { + case getUploadedCourseDetail(publicCourseId: Int) +} + +extension UploadedCourseDetailRouter: TargetType { + var baseURL: URL { + guard let url = URL(string: Config.baseURL) else { + fatalError("baseURL could not be configured") + } + + return url + } + + var path: String { + switch self { + case .getUploadedCourseDetail(let publicCourseId): + return "/public-course/detail/\(publicCourseId)" + } + } + + var method: Moya.Method { + switch self { + case .getUploadedCourseDetail: + return .get + } + } + + var task: Moya.Task { + switch self { + case .getUploadedCourseDetail: + return .requestPlain + } + } + + var headers: [String: String]? { + switch self { + case .getUploadedCourseDetail: + return Config.headerWithDeviceId + } + } +} diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/.gitkeep b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift index 1754df7d..c3b046f5 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift @@ -6,13 +6,26 @@ // import UIKit + import SnapKit import Then +import NMapsMap +import Moya final class CourseDetailVC: UIViewController { // MARK: - Properties + private let courseDetailProvider = MoyaProvider( + plugins: [NetworkLoggerPlugin(verbose: true)] + ) + + private let runningProvider = MoyaProvider( + plugins: [NetworkLoggerPlugin(verbose: true)] + ) + + private var courseModel: Course? + private var courseId: Int? private var publicCourseId: Int? @@ -35,11 +48,11 @@ final class CourseDetailVC: UIViewController { } private lazy var startButton = CustomButton(title: "시작하기").then { - $0.addTarget(self, action: #selector(pushToCountDownVC), for: .touchUpInside) + $0.addTarget(self, action: #selector(startButtonDidTap), for: .touchUpInside) } - private let mapImage = UIImageView() - private let profileImage = UIImageView().then { + private let mapImageView = UIImageView() + private let profileImageView = UIImageView().then { $0.image = ImageLiterals.imgStampC3 $0.layer.cornerRadius = 20 $0.layer.borderWidth = 1 @@ -47,35 +60,35 @@ final class CourseDetailVC: UIViewController { } private let profileNameLabel = UILabel().then { - $0.text = "말랑콩떡" + $0.text = "닉네임" $0.textColor = .g1 $0.font = .h5 } private let runningLevelLabel = UILabel().then { - $0.text = "Lv. 3" + $0.text = "Lv. 0" $0.textColor = .m1 $0.font = .b5 } private let courseTitleLabel = UILabel().then { - $0.text = "잠실 석촌호수 한 바퀴 러닝" + $0.text = "제목" $0.textColor = .g1 $0.font = .h4 } - private let courseDistanceLabel = CourseDetailInfoView(title: "거리", description: "2.3km") + private let courseDistanceInfoView = CourseDetailInfoView(title: "거리", description: "0.0km") - private let courseDepartureLabel = CourseDetailInfoView(title: "출발지", description: "패스트파이브 을지로점") + private let courseDepartureInfoView = CourseDetailInfoView(title: "출발지", description: "위치") - private lazy var courseDetailStackView = UIStackView(arrangedSubviews: [courseDistanceLabel, courseDepartureLabel]).then { + private lazy var courseDetailStackView = UIStackView(arrangedSubviews: [courseDistanceInfoView, courseDepartureInfoView]).then { $0.axis = .vertical $0.spacing = 6 $0.distribution = .fillEqually } private lazy var courseExplanationTextView = UITextView().then { - $0.text = "석촌 호수 한 바퀴 뛰는 코스에요! 평탄한 길과 느린 페이스, 난이도 하 코스입니다! 롯데월드 야경 감상 하면서 뛰기에 좋은 야간 코스에요! 석촌 호수 한 바퀴 뛰는 코스에요! 평탄한 길과 느린 페이스, 난이도 하 코스입니다! 롯데월드 야경 감상 하면서 뛰기에 좋은" + $0.text = "설명" $0.isEditable = false let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineSpacing = 10 @@ -94,6 +107,7 @@ final class CourseDetailVC: UIViewController { setUI() setLayout() setAddTarget() + getUploadedCourseDetail() } } @@ -104,11 +118,28 @@ extension CourseDetailVC { sender.isSelected.toggle() } - @objc private func pushToCountDownVC() { + @objc func startButtonDidTap() { + guard let courseId = self.courseId else { return } + getCourseDetailWithPath(courseId: courseId) + } + + private func pushToCountDownVC() { + guard let courseModel = self.courseModel, + let path = courseModel.path, + let distance = courseModel.distance + else { return } + let countDownVC = CountDownVC() - let runningModel = RunningModel(locations: [], - distance: "1.0", - pathImage: UIImage()) + let locations = path.map { NMGLatLng(lat: $0[0], lng: $0[1]) } + + let runningModel = RunningModel(courseId: self.courseId, + publicCourseId: self.publicCourseId, + locations: locations, + distance: String(distance), + imageUrl: courseModel.image, + region: courseModel.departure.region, + city: courseModel.departure.city) + countDownVC.setData(runningModel: runningModel) self.navigationController?.pushViewController(countDownVC, animated: true) } @@ -122,6 +153,20 @@ extension CourseDetailVC { self.publicCourseId = publicCourseId } + func setData(model: UploadedCourseDetailResponseDto) { + self.mapImageView.setImage(with: model.publicCourse.image) + self.profileImageView.image = GoalRewardInfoModel.stampNameImageDictionary[model.user.image] + self.profileNameLabel.text = model.user.nickname + self.runningLevelLabel.text = "Lv. \(model.user.level)" + self.courseTitleLabel.text = model.publicCourse.title + + guard let distance = model.publicCourse.distance else { return } + self.courseDistanceInfoView.setDescriptionText(description: String(distance)) + let location = "\(model.publicCourse.departure.region) \(model.publicCourse.departure.city)" + self.courseDepartureInfoView.setDescriptionText(description: location) + self.courseExplanationTextView.text = model.publicCourse.description + } + private func setAddTarget() { likeButton.addTarget(self, action: #selector(likeButtonDidTap), for: .touchUpInside) } @@ -143,7 +188,7 @@ extension CourseDetailVC { view.backgroundColor = .w1 bottomView.backgroundColor = .w1 middleScorollView.backgroundColor = .w1 - mapImage.backgroundColor = .g3 + mapImageView.backgroundColor = .g3 firstHorizontalDivideLine.backgroundColor = .g3 secondHorizontalDivideLine.backgroundColor = .g5 thirdHorizontalDivideLine.backgroundColor = .g3 @@ -189,23 +234,23 @@ extension CourseDetailVC { make.bottom.equalTo(thirdHorizontalDivideLine.snp.top) } - middleScorollView.addSubviews(mapImage, profileImage, profileNameLabel, runningLevelLabel, firstHorizontalDivideLine, courseTitleLabel, courseDetailStackView, secondHorizontalDivideLine, courseExplanationTextView) + middleScorollView.addSubviews(mapImageView, profileImageView, profileNameLabel, runningLevelLabel, firstHorizontalDivideLine, courseTitleLabel, courseDetailStackView, secondHorizontalDivideLine, courseExplanationTextView) - mapImage.snp.makeConstraints { make in + mapImageView.snp.makeConstraints { make in make.top.equalToSuperview() make.leading.trailing.equalTo(view.safeAreaLayoutGuide) make.height.equalTo(middleScorollView.snp.width).multipliedBy(0.7) } - profileImage.snp.makeConstraints { make in - make.top.equalTo(mapImage.snp.bottom).offset(14) + profileImageView.snp.makeConstraints { make in + make.top.equalTo(mapImageView.snp.bottom).offset(14) make.leading.equalToSuperview().offset(14) make.width.height.equalTo(34) } profileNameLabel.snp.makeConstraints { make in - make.centerY.equalTo(profileImage.snp.centerY) - make.leading.equalTo(profileImage.snp.trailing).offset(12) + make.centerY.equalTo(profileImageView.snp.centerY) + make.leading.equalTo(profileImageView.snp.trailing).offset(12) } runningLevelLabel.snp.makeConstraints { make in @@ -214,7 +259,7 @@ extension CourseDetailVC { } firstHorizontalDivideLine.snp.makeConstraints { make in - make.top.equalTo(mapImage.snp.bottom).offset(62) + make.top.equalTo(mapImageView.snp.bottom).offset(62) make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(14) make.height.equalTo(0.5) } @@ -242,3 +287,64 @@ extension CourseDetailVC { } } } + +extension CourseDetailVC { + private func getUploadedCourseDetail() { + guard let publicCourseId = self.publicCourseId else { return } + LoadingIndicator.showLoading() + courseDetailProvider.request(.getUploadedCourseDetail(publicCourseId: publicCourseId)) { [weak self] response in + guard let self = self else { return } + LoadingIndicator.hideLoading() + switch response { + case .success(let result): + let status = result.statusCode + if 200..<300 ~= status { + do { + let responseDto = try result.map(BaseResponse.self) + guard let data = responseDto.data else { return } + self.setData(model: data) + } catch { + print(error.localizedDescription) + } + } + if status >= 400 { + print("400 error") + self.showNetworkFailureToast() + } + case .failure(let error): + print(error.localizedDescription) + self.showNetworkFailureToast() + } + } + } + + private func getCourseDetailWithPath(courseId: Int) { + LoadingIndicator.showLoading() + + runningProvider.request(.getCourseDetail(courseId: courseId)) { [weak self] response in + guard let self = self else { return } + LoadingIndicator.hideLoading() + switch response { + case .success(let result): + let status = result.statusCode + if 200..<300 ~= status { + do { + let responseDto = try result.map(BaseResponse.self) + guard let data = responseDto.data else { return } + self.courseModel = data.course + self.pushToCountDownVC() + } catch { + print(error.localizedDescription) + } + } + if status >= 400 { + print("400 error") + self.showNetworkFailureToast() + } + case .failure(let error): + print(error.localizedDescription) + self.showNetworkFailureToast() + } + } + } +} diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseDiscoveryVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseDiscoveryVC.swift index a99b0c08..75115340 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseDiscoveryVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseDiscoveryVC.swift @@ -222,6 +222,9 @@ extension CourseDiscoveryVC: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if indexPath.section == 2 { let courseDetailVC = CourseDetailVC() + let courseModel = courseList[indexPath.item] + courseDetailVC.setCourseId(courseId: courseModel.courseId, publicCourseId: courseModel.id) + courseDetailVC.hidesBottomBarWhenPushed = true self.navigationController?.pushViewController(courseDetailVC, animated: true) } }