From a7e8c741942fd686c365425b1e8ca3e537026efc Mon Sep 17 00:00:00 2001 From: youz2me Date: Fri, 19 Jul 2024 11:56:46 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat/#240=20=EC=A7=80=EA=B0=81=20=EA=BE=B8?= =?UTF-8?q?=EB=AC=BC=EC=9D=B4=20API=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PagePromiseViewController.swift | 2 +- .../Tardy/Cell/TardyCollectionViewCell.swift | 4 +- .../Promise/Tardy/View/TardyEmptyView.swift | 2 + .../Promise/Tardy/View/TardyPenaltyView.swift | 2 +- .../Source/Promise/Tardy/View/TardyView.swift | 6 +- .../ViewController/TardyViewController.swift | 63 +++++++++++++------ .../Tardy/ViewModel/TardyViewModel.swift | 58 +++++++++++++++-- 7 files changed, 105 insertions(+), 32 deletions(-) diff --git a/KkuMulKum/Source/Promise/PagePromise/ViewController/PagePromiseViewController.swift b/KkuMulKum/Source/Promise/PagePromise/ViewController/PagePromiseViewController.swift index 28647264..1ca9c592 100644 --- a/KkuMulKum/Source/Promise/PagePromise/ViewController/PagePromiseViewController.swift +++ b/KkuMulKum/Source/Promise/PagePromise/ViewController/PagePromiseViewController.swift @@ -63,7 +63,7 @@ class PagePromiseViewController: BaseViewController { tardyViewController = TardyViewController( tardyViewModel: TardyViewModel( - tardyService: MockTardyService(), + tardyService: PromiseService(), promiseID: promiseViewModel.promiseID ) ) diff --git a/KkuMulKum/Source/Promise/Tardy/Cell/TardyCollectionViewCell.swift b/KkuMulKum/Source/Promise/Tardy/Cell/TardyCollectionViewCell.swift index ebc2d60e..35642737 100644 --- a/KkuMulKum/Source/Promise/Tardy/Cell/TardyCollectionViewCell.swift +++ b/KkuMulKum/Source/Promise/Tardy/Cell/TardyCollectionViewCell.swift @@ -12,10 +12,10 @@ class TardyCollectionViewCell: BaseCollectionViewCell { // MARK: Property - private let profileImageView: UIImageView = UIImageView().then { + let profileImageView: UIImageView = UIImageView().then { $0.image = .imgProfile $0.contentMode = .scaleAspectFill - $0.layer.cornerRadius = 67 / 2 + $0.layer.cornerRadius = Screen.height(67) / 2 $0.clipsToBounds = true } diff --git a/KkuMulKum/Source/Promise/Tardy/View/TardyEmptyView.swift b/KkuMulKum/Source/Promise/Tardy/View/TardyEmptyView.swift index 84fba014..80f84356 100644 --- a/KkuMulKum/Source/Promise/Tardy/View/TardyEmptyView.swift +++ b/KkuMulKum/Source/Promise/Tardy/View/TardyEmptyView.swift @@ -19,6 +19,8 @@ class TardyEmptyView: BaseView { private let emptyContentLabel: UILabel = UILabel().then { $0.setText("꾸물이들이 도착하길\n기다리는 중이에요", style: .body05, color: .gray3) + }.then { + $0.textAlignment = .center } diff --git a/KkuMulKum/Source/Promise/Tardy/View/TardyPenaltyView.swift b/KkuMulKum/Source/Promise/Tardy/View/TardyPenaltyView.swift index 9d495b10..1eabdf48 100644 --- a/KkuMulKum/Source/Promise/Tardy/View/TardyPenaltyView.swift +++ b/KkuMulKum/Source/Promise/Tardy/View/TardyPenaltyView.swift @@ -21,7 +21,7 @@ class TardyPenaltyView: BaseView { $0.setText("벌칙", style: .caption02, color: .gray8) } - private let contentLabel: UILabel = UILabel().then { + let contentLabel: UILabel = UILabel().then { $0.setText("탕후루 릴스 찍기", style: .body03, color: .gray8) } diff --git a/KkuMulKum/Source/Promise/Tardy/View/TardyView.swift b/KkuMulKum/Source/Promise/Tardy/View/TardyView.swift index a788b7bb..eb2f9210 100644 --- a/KkuMulKum/Source/Promise/Tardy/View/TardyView.swift +++ b/KkuMulKum/Source/Promise/Tardy/View/TardyView.swift @@ -12,7 +12,7 @@ class TardyView: BaseView { // MARK: Property - private let tardyPenaltyView: TardyPenaltyView = TardyPenaltyView().then { + let tardyPenaltyView: TardyPenaltyView = TardyPenaltyView().then { $0.layer.cornerRadius = 8 } @@ -20,7 +20,7 @@ class TardyView: BaseView { $0.setText("이번 약속의 지각 꾸물이는?", style: .head01, color: .gray8) } - private let tardyEmptyView: TardyEmptyView = TardyEmptyView() + let tardyEmptyView: TardyEmptyView = TardyEmptyView() let tardyCollectionView: UICollectionView = UICollectionView( frame: .zero, @@ -74,7 +74,7 @@ class TardyView: BaseView { tardyEmptyView.snp.makeConstraints { $0.top.equalTo(titleLabel.snp.bottom).offset(108) $0.centerX.equalToSuperview() - $0.height.equalTo(Screen.height(192)) + $0.height.equalTo(Screen.height(210)) $0.width.equalTo(Screen.width(112)) } diff --git a/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift b/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift index 6ca53d45..41c62287 100644 --- a/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift +++ b/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift @@ -33,14 +33,14 @@ class TardyViewController: BaseViewController { // MARK: - Setup override func loadView() { - let state = !tardyViewModel.hasTardy.value && tardyViewModel.isPastDue.value - view = state ? tardyView : arriveView + view = tardyViewModel.isPastDue.value ? arriveView : tardyView } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - // TODO: 서버 통신하고 데이터 바인딩 + tardyViewModel.fetchTardyInfo() + tardyViewModel.updatePromiseCompletion() } override func viewDidLoad() { @@ -50,7 +50,6 @@ class TardyViewController: BaseViewController { } override func setupDelegate() { - tardyView.tardyCollectionView.delegate = self tardyView.tardyCollectionView.dataSource = self } } @@ -61,25 +60,40 @@ class TardyViewController: BaseViewController { private extension TardyViewController { func setupBinding() { /// 시간이 지나고 지각자가 없을 때 arriveView로 띄워짐 - tardyViewModel.hasTardy.bind(with: self) { owner, flag in - let state = !flag && owner.tardyViewModel.isPastDue.value - owner.view = state ? owner.tardyView : owner.arriveView + tardyViewModel.isPastDue.bind(with: self) { owner, isPastDue in + DispatchQueue.main.async { + owner.tardyView.tardyCollectionView.isHidden = !isPastDue + owner.tardyView.tardyEmptyView.isHidden = isPastDue + owner.tardyView.finishMeetingButton.isEnabled = isPastDue + } } - /// isFinishButtonEnabled에 따라서 버튼 활성화 상태 변경 - tardyViewModel.isFinishButtonEnabled.bind(with: self) { owner, flag in - self.tardyView.finishMeetingButton.isEnabled = flag + tardyViewModel.penalty.bind(with: self) { + owner, + penalty in + DispatchQueue.main.async { + owner.tardyView.tardyPenaltyView.contentLabel.setText( + penalty, + style: .body03, + color: .gray8 + ) + } + } + + tardyViewModel.hasTardy.bind(with: self) { owner, hasTardy in + DispatchQueue.main.async { + owner.view = hasTardy && owner.tardyViewModel.isPastDue.value ? owner.arriveView : owner.tardyView + } + } + + tardyViewModel.comers.bind(with: self) { owner, comers in + DispatchQueue.main.async { + owner.tardyView.tardyCollectionView.reloadData() + } } } } -// MARK: UICollectionViewDelegate - -extension TardyViewController: UICollectionViewDelegate { - -} - - // MARK: UICollectionViewDataSource extension TardyViewController: UICollectionViewDataSource { @@ -87,8 +101,7 @@ extension TardyViewController: UICollectionViewDataSource { _ collectionView: UICollectionView, numberOfItemsInSection section: Int ) -> Int { - // TODO: 데이터 바인딩 - return 10 + return tardyViewModel.comers.value?.count ?? 0 } func collectionView( @@ -100,6 +113,18 @@ extension TardyViewController: UICollectionViewDataSource { for: indexPath ) as? TardyCollectionViewCell else { return UICollectionViewCell() } + guard let data = tardyViewModel.comers.value?[indexPath.row] else { return cell } + + cell.nameLabel.setText(data.name, style: .body06, color: .gray6) + + guard let image = URL(string: data.profileImageURL) else { + cell.profileImageView.image = .imgProfile + + return cell + } + + cell.profileImageView.kf.setImage(with: image) + return cell } } diff --git a/KkuMulKum/Source/Promise/Tardy/ViewModel/TardyViewModel.swift b/KkuMulKum/Source/Promise/Tardy/ViewModel/TardyViewModel.swift index c03ecb79..55867fea 100644 --- a/KkuMulKum/Source/Promise/Tardy/ViewModel/TardyViewModel.swift +++ b/KkuMulKum/Source/Promise/Tardy/ViewModel/TardyViewModel.swift @@ -12,11 +12,13 @@ class TardyViewModel { // MARK: Property - let tardyService: TardyServiceType - var isPastDue: ObservablePattern = ObservablePattern(true) - var hasTardy: ObservablePattern = ObservablePattern(true) - var isFinishButtonEnabled: ObservablePattern = ObservablePattern(true) - let promiseID: ObservablePattern + private let tardyService: TardyServiceType + let promiseID: Int + + var hasTardy: ObservablePattern = ObservablePattern(false) + var isPastDue: ObservablePattern = ObservablePattern(false) + var penalty: ObservablePattern = ObservablePattern("") + var comers: ObservablePattern<[Comer]?> = ObservablePattern<[Comer]?>(nil) // MARK: Initialize @@ -26,6 +28,50 @@ class TardyViewModel { promiseID: Int ) { self.tardyService = tardyService - self.promiseID = ObservablePattern(promiseID) + self.promiseID = promiseID + } +} + +extension TardyViewModel { + func fetchTardyInfo() { + Task { + do { + let responseBody = try await + tardyService.fetchTardyInfo(with: promiseID) + + guard let success = responseBody?.success, success == true + else { + return + } + + guard let data = responseBody?.data else { + return + } + + hasTardy.value = data.lateComers.isEmpty + isPastDue.value = data.isPastDue + penalty.value = data.penalty + comers.value = data.lateComers + + } catch { + print(">>>>> \(error.localizedDescription) : \(#function)") + } + } + } + + func updatePromiseCompletion() { + Task { + do { + let responseBody = try await + tardyService.updatePromiseCompletion(with: promiseID) + + guard let success = responseBody?.success, success == true + else { + return + } + } catch { + print(">>>>> \(error.localizedDescription) : \(#function)") + } + } } } From b292e1d0335c8e0dee15f2e8032163b4fa47d708 Mon Sep 17 00:00:00 2001 From: youz2me Date: Fri, 19 Jul 2024 14:31:43 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat/#240=20=EC=A4=80=EB=B9=84=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=8B=9C=EA=B0=84=20=EC=84=A4=EC=A0=95=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReadyStatus/View/ReadyPlanInfoView.swift | 10 +-- .../ReadyStatusViewController.swift | 30 +++++++-- .../ViewModel/ReadyStatusViewModel.swift | 64 ++++++++++++++++++- 3 files changed, 90 insertions(+), 14 deletions(-) diff --git a/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyPlanInfoView.swift b/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyPlanInfoView.swift index 06336de2..879277fe 100644 --- a/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyPlanInfoView.swift +++ b/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyPlanInfoView.swift @@ -12,16 +12,16 @@ class ReadyPlanInfoView: BaseView { // MARK: Property - private let readyTimeLabel: UILabel = UILabel().then { + let readyTimeLabel: UILabel = UILabel().then { $0.setText("12시 30분에 준비하고,\n1시에 이동을 시작해야 해요", style: .body03) $0.setHighlightText("12시 30분", "1시", style: .body03, color: .maincolor) } - private let requestReadyTimeLabel: UILabel = UILabel().then { + let requestReadyTimeLabel: UILabel = UILabel().then { $0.setText("준비 소요 시간: 30분", style: .label02, color: .gray8) } - private let requestMoveTimeLabel: UILabel = UILabel().then { + let requestMoveTimeLabel: UILabel = UILabel().then { $0.setText("이동 소요 시간: 1시간 30분", style: .label02, color: .gray8) } @@ -69,7 +69,3 @@ class ReadyPlanInfoView: BaseView { } } } - -extension ReadyPlanInfoView { - func configure() {} -} diff --git a/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift b/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift index 78a267df..9208109c 100644 --- a/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift +++ b/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift @@ -73,7 +73,6 @@ class ReadyStatusViewController: BaseViewController { @objc func readyStartButtonDidTapped() { - // TODO: 늦었을 때 꾸물거릴 시간이 없어요 팝업 뜨도록 설정 readyStatusViewModel.myReadyProgressStatus.value = .ready rootView.myReadyStatusProgressView.readyStartButton.isEnabled.toggle() } @@ -174,9 +173,32 @@ private extension ReadyStatusViewController { owner.updateReadyInfoView(flag: false) return } - // TODO: 시간 계산 로직 필요.. + owner.updateReadyInfoView(flag: true) - owner.rootView.readyPlanInfoView.configure() + owner.readyStatusViewModel.calculatePrepareTime() + owner.readyStatusViewModel.convertMinute() + } + } + + readyStatusViewModel.moveTime.bind(with: self) { owner, moveTime in + owner.rootView.readyPlanInfoView.requestMoveTimeLabel.setText("이동 소요 시간: \(moveTime)", style: .label02, color: .gray8) + } + + readyStatusViewModel.readyTime.bind(with: self) { owner, readyTime in + owner.rootView.readyPlanInfoView.requestReadyTimeLabel.setText("준비 소요 시간: \(readyTime)", style: .label02, color: .gray8) + } + + readyStatusViewModel.readyStartTime.bind(with: self) { owner, readyStartTime in + DispatchQueue.main.async { + owner.rootView.readyPlanInfoView.readyTimeLabel.setText("\(readyStartTime)에 준비하고,\n\(owner.readyStatusViewModel.moveStartTime.value)에 이동을 시작해야 해요", style: .body03) + owner.rootView.readyPlanInfoView.readyTimeLabel.setHighlightText(readyStartTime, owner.readyStatusViewModel.moveStartTime.value, style: .body03, color: .maincolor) + } + } + + readyStatusViewModel.moveStartTime.bind(with: self) { owner, moveStartTime in + DispatchQueue.main.async { + owner.rootView.readyPlanInfoView.readyTimeLabel.setText("\(owner.readyStatusViewModel.readyStartTime.value)에 준비하고,\n\(moveStartTime)에 이동을 시작해야 해요", style: .body03) + owner.rootView.readyPlanInfoView.readyTimeLabel.setHighlightText(owner.readyStatusViewModel.readyStartTime.value, moveStartTime, style: .body03, color: .maincolor) } } @@ -211,7 +233,7 @@ private extension ReadyStatusViewController { } func updateReadyStartButton(status: ReadyProgressStatus) { - /// 버튼 누를 때 서버 통신하게 설정 + // TODO: 버튼 누를 때 서버 통신하게 설정 switch status { case .none: rootView.myReadyStatusProgressView.readyStartButton.setupButton( diff --git a/KkuMulKum/Source/Promise/ReadyStatus/ViewModel/ReadyStatusViewModel.swift b/KkuMulKum/Source/Promise/ReadyStatus/ViewModel/ReadyStatusViewModel.swift index f0e82c47..2a64ce30 100644 --- a/KkuMulKum/Source/Promise/ReadyStatus/ViewModel/ReadyStatusViewModel.swift +++ b/KkuMulKum/Source/Promise/ReadyStatus/ViewModel/ReadyStatusViewModel.swift @@ -17,13 +17,22 @@ class ReadyStatusViewModel { /// bind를 사용하라는 말이 아님 let promiseName = ObservablePattern("") - // 준비 정보가 입력되었는지 여부 -// let isReadyInfoEntered = ObservablePattern(false) - /// 나의 준비현황이 담긴 정보 /// 설령 데이터가 없다하더라도 약속 시간은 담겨있음. let myReadyStatus = ObservablePattern(nil) + // 준비 시작 시간 + var readyStartTime = ObservablePattern("") + + // 준비 소요 시간 + var readyTime = ObservablePattern("") + + // 이동 시작 시간 + var moveStartTime = ObservablePattern("") + + // 이동 소요 시간 + var moveTime = ObservablePattern("") + // 현재 준비 상태에 대한 버튼 처리 let myReadyProgressStatus = ObservablePattern(.none) @@ -63,6 +72,55 @@ class ReadyStatusViewModel { } extension ReadyStatusViewModel { + func convertMinute() { + let preparationHours = (self.myReadyStatus.value?.preparationTime ?? 0) / 60 + let preparationMinutes = (self.myReadyStatus.value?.preparationTime ?? 0) % 60 + + readyTime.value = preparationHours == 0 ? "\(preparationMinutes)분" : "\(preparationHours)시간 \(preparationMinutes)분" + + let travelHours = (self.myReadyStatus.value?.travelTime ?? 0) / 60 + let travelMinutes = (self.myReadyStatus.value?.travelTime ?? 0) % 60 + + moveTime.value = travelHours == 0 ? "\(travelMinutes)분" : "\(travelHours)시간 \(travelMinutes)분" + } + + func calculatePrepareTime() { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + dateFormatter.locale = Locale(identifier: "ko_KR") + dateFormatter.timeZone = TimeZone(identifier: "Asia/Seoul") + + let promiseTime = self.myReadyStatus.value?.promiseTime ?? "" + let readyTime = self.myReadyStatus.value?.preparationTime ?? 0 + let moveTime = self.myReadyStatus.value?.travelTime ?? 0 + + + guard let promiseDate = dateFormatter.date(from: promiseTime) else { + print("Invalid date format: \(promiseTime)") + return + } + + let totalPrepTime = TimeInterval((readyTime + moveTime) * 60) + + let timeFormatter = DateFormatter() + timeFormatter.dateFormat = "HH시 mm분" + timeFormatter.timeZone = TimeZone(identifier: "Asia/Seoul") + + print("약속 시간: \(timeFormatter.string(from: promiseDate))") + print("준비 시간: \(readyTime) 분") + print("이동 시간: \(moveTime) 분") + print("총 준비 시간: \(totalPrepTime / 60) 분") + + let readyStartTime = promiseDate.addingTimeInterval(-TimeInterval(readyTime + moveTime) * 60) + let moveStartTime = promiseDate.addingTimeInterval(-TimeInterval(moveTime) * 60) + + self.readyStartTime.value = timeFormatter.string(from: readyStartTime) + print("준비 시작 시간: \(self.readyStartTime.value)") + + self.moveStartTime.value = timeFormatter.string(from: moveStartTime) + print("이동 시작 시간: \(self.moveStartTime.value)") + } + func fetchMyReadyStatus() { Task { do {