diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index d019b288..f6050b5b 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -152,7 +152,7 @@ DD43937C2C412F4500EC1799 /* InviteCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4393742C412F4500EC1799 /* InviteCodeViewController.swift */; }; DD43937D2C412F4500EC1799 /* CheckInviteCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4393752C412F4500EC1799 /* CheckInviteCodeViewController.swift */; }; DD43937F2C41357800EC1799 /* InviteCodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD43937E2C41357800EC1799 /* InviteCodeViewModel.swift */; }; - DD4909962C440CDC003ED304 /* ArriveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4909952C440CDC003ED304 /* ArriveView.swift */; }; + DD4909962C440CDC003ED304 /* NoTardyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4909952C440CDC003ED304 /* NoTardyView.swift */; }; DD8626612C4606A300E4F980 /* SetReadyInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8626522C4606A300E4F980 /* SetReadyInfoViewModel.swift */; }; DD8626632C4606A300E4F980 /* EnterReadyInfoButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8626552C4606A300E4F980 /* EnterReadyInfoButtonView.swift */; }; DD8626642C4606A300E4F980 /* ReadyPlanInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8626562C4606A300E4F980 /* ReadyPlanInfoView.swift */; }; @@ -371,7 +371,7 @@ DD4393742C412F4500EC1799 /* InviteCodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InviteCodeViewController.swift; sourceTree = ""; }; DD4393752C412F4500EC1799 /* CheckInviteCodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckInviteCodeViewController.swift; sourceTree = ""; }; DD43937E2C41357800EC1799 /* InviteCodeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteCodeViewModel.swift; sourceTree = ""; }; - DD4909952C440CDC003ED304 /* ArriveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArriveView.swift; sourceTree = ""; }; + DD4909952C440CDC003ED304 /* NoTardyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoTardyView.swift; sourceTree = ""; }; DD8626522C4606A300E4F980 /* SetReadyInfoViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetReadyInfoViewModel.swift; sourceTree = ""; }; DD8626552C4606A300E4F980 /* EnterReadyInfoButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterReadyInfoButtonView.swift; sourceTree = ""; }; DD8626562C4606A300E4F980 /* ReadyPlanInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadyPlanInfoView.swift; sourceTree = ""; }; @@ -803,7 +803,7 @@ DD41BEF92C41D4160095A068 /* TardyView.swift */, DD41BEFB2C41D54D0095A068 /* TardyPenaltyView.swift */, DD41BEFE2C41DAA40095A068 /* TardyEmptyView.swift */, - DD4909952C440CDC003ED304 /* ArriveView.swift */, + DD4909952C440CDC003ED304 /* NoTardyView.swift */, ); path = View; sourceTree = ""; @@ -2039,7 +2039,7 @@ DE9E18802C3BA4AA00DB76B4 /* CustomButton.swift in Sources */, DE0137D32C43C5E50088C777 /* MyPageView.swift in Sources */, DE558C592C45954B008DAC4A /* SelectMemberViewController.swift in Sources */, - DD4909962C440CDC003ED304 /* ArriveView.swift in Sources */, + DD4909962C440CDC003ED304 /* NoTardyView.swift in Sources */, 789196342C486F6B00FF8CDF /* KeychainAccessible.swift in Sources */, DE254AB02C31195B00A4015E /* NSAttributedString+.swift in Sources */, DD43937B2C412F4500EC1799 /* CreateMeetingViewController.swift in Sources */, diff --git a/KkuMulKum/Resource/ObservablePattern/ObservablePattern.swift b/KkuMulKum/Resource/ObservablePattern/ObservablePattern.swift index a6829897..05cbed39 100644 --- a/KkuMulKum/Resource/ObservablePattern/ObservablePattern.swift +++ b/KkuMulKum/Resource/ObservablePattern/ObservablePattern.swift @@ -30,8 +30,6 @@ class ObservablePattern { func bind(_ listener: @escaping (T) -> Void) { listeners.append(listener) - - listener(value) } func bind(with object: Object, _ listener: @escaping (Object, T) -> Void) { @@ -39,8 +37,6 @@ class ObservablePattern { guard let object else { return } listener(object, value) } - - listener(object, value) } func bindOnMain(_ listener: @escaping (T) -> Void) { @@ -49,10 +45,6 @@ class ObservablePattern { listener(value) } } - - DispatchQueue.main.async { - listener(self.value) - } } func bindOnMain(with object: Object, _ listener: @escaping (Object, T) -> Void) { @@ -62,9 +54,5 @@ class ObservablePattern { listener(object, value) } } - - DispatchQueue.main.async { - listener(object, self.value) - } } } diff --git a/KkuMulKum/Source/AddMeeting/CreateMeeting/ViewController/CreateMeetingViewController.swift b/KkuMulKum/Source/AddMeeting/CreateMeeting/ViewController/CreateMeetingViewController.swift index 41ac462d..44b18ae5 100644 --- a/KkuMulKum/Source/AddMeeting/CreateMeeting/ViewController/CreateMeetingViewController.swift +++ b/KkuMulKum/Source/AddMeeting/CreateMeeting/ViewController/CreateMeetingViewController.swift @@ -77,6 +77,10 @@ class CreateMeetingViewController: BaseViewController { ) ) } + + override func setupDelegate() { + rootView.nameTextField.delegate = self + } } @@ -91,8 +95,12 @@ private extension CreateMeetingViewController { switch state { case .valid: owner.rootView.presentButton.isEnabled = true + owner.rootView.nameTextField.layer.borderColor = UIColor.maincolor.cgColor + owner.rootView.characterLabel.textColor = .maincolor case .invalid: owner.rootView.errorLabel.isHidden = false + owner.rootView.nameTextField.layer.borderColor = UIColor.mainred.cgColor + owner.rootView.characterLabel.textColor = .mainred case .empty: break } @@ -178,3 +186,14 @@ private extension CreateMeetingViewController { } } } + +extension CreateMeetingViewController: UITextFieldDelegate { + func textFieldDidBeginEditing(_ textField: UITextField) { + textField.layer.borderColor = UIColor.maincolor.cgColor + } + + func textFieldDidEndEditing(_ textField: UITextField) { + rootView.characterLabel.textColor = .gray3 + rootView.nameTextField.layer.borderColor = UIColor.gray3.cgColor + } +} diff --git a/KkuMulKum/Source/Promise/EditPromise/ViewController/ChooseContentViewController.swift b/KkuMulKum/Source/Promise/EditPromise/ViewController/ChooseContentViewController.swift index a11b161f..c0d0231a 100644 --- a/KkuMulKum/Source/Promise/EditPromise/ViewController/ChooseContentViewController.swift +++ b/KkuMulKum/Source/Promise/EditPromise/ViewController/ChooseContentViewController.swift @@ -106,12 +106,14 @@ private extension ChooseContentViewController { }) viewModel.isSuccess.bindOnMain(with: self) { owner, success in - let viewController = AddPromiseCompleteViewController(promiseID: self.viewModel.promiseID) - - viewController.setupNavigationBarTitle(with: "약속 수정하기") - viewController.rootView.titleLabel.text = "약속이 수정되었어요!" - - self.navigationController?.pushViewController(viewController, animated: true) + if success { + let viewController = AddPromiseCompleteViewController(promiseID: owner.viewModel.promiseID) + + viewController.setupNavigationBarTitle(with: "약속 수정하기") + viewController.rootView.titleLabel.text = "약속이 수정되었어요!" + + self.navigationController?.pushViewController(viewController, animated: true) + } } } diff --git a/KkuMulKum/Source/Promise/EditPromise/ViewController/EditPromiseViewController.swift b/KkuMulKum/Source/Promise/EditPromise/ViewController/EditPromiseViewController.swift index b3963e06..f8459509 100644 --- a/KkuMulKum/Source/Promise/EditPromise/ViewController/EditPromiseViewController.swift +++ b/KkuMulKum/Source/Promise/EditPromise/ViewController/EditPromiseViewController.swift @@ -116,7 +116,7 @@ private extension EditPromiseViewController { return } - rootView.datePicker.minimumDate = .none + rootView.datePicker.minimumDate = .now rootView.datePicker.date = date rootView.timePicker.date = date } diff --git a/KkuMulKum/Source/Promise/PagePromise/ViewController/PromiseViewController.swift b/KkuMulKum/Source/Promise/PagePromise/ViewController/PromiseViewController.swift index 4d53e4ee..c4f873b9 100644 --- a/KkuMulKum/Source/Promise/PagePromise/ViewController/PromiseViewController.swift +++ b/KkuMulKum/Source/Promise/PagePromise/ViewController/PromiseViewController.swift @@ -22,13 +22,9 @@ class PromiseViewController: BaseViewController { transitionStyle: .scroll, navigationOrientation: .vertical ) - private var removePromiseViewContoller: RemovePromiseViewController = RemovePromiseViewController(promiseName: "") private var promiseViewControllerList: [BaseViewController] = [] - - private lazy var promiseSegmentedControl = PagePromiseSegmentedControl( - items: ["약속 정보", "준비 현황", "지각 꾸물이"] - ) + private lazy var promiseSegmentedControl = PagePromiseSegmentedControl(items: ["약속 정보", "준비 현황", "지각 꾸물이"]) // MARK: - LifeCycle @@ -57,8 +53,7 @@ class PromiseViewController: BaseViewController { super.viewDidLoad() setupNavigationBarBackButton() - setupPromiseEditButton(isHidden: false) - setupBindings() + setupBinding() } override func viewWillAppear(_ animated: Bool) { @@ -75,16 +70,13 @@ class PromiseViewController: BaseViewController { // MARK: - Setup - + override func setupView() { view.backgroundColor = .white addChild(promisePageViewController) - view.addSubviews( - promiseSegmentedControl, - promisePageViewController.view - ) + view.addSubviews(promiseSegmentedControl, promisePageViewController.view) promisePageViewController.setViewControllers( [promiseViewControllerList[0]], @@ -95,7 +87,7 @@ class PromiseViewController: BaseViewController { promiseSegmentedControl.snp.makeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide) $0.leading.trailing.equalToSuperview().inset(-6) - $0.height.equalTo(60) + $0.height.equalTo(Screen.height(60)) } promisePageViewController.view.snp.makeConstraints { @@ -111,13 +103,7 @@ class PromiseViewController: BaseViewController { for: .valueChanged ) - promiseTardyViewController.tardyView.finishMeetingButton.addTarget( - self, - action: #selector(finishMeetingButtonDidTap), - for: .touchUpInside - ) - - promiseTardyViewController.arriveView.finishMeetingButton.addTarget( + promiseTardyViewController.rootView.finishMeetingButton.addTarget( self, action: #selector(finishMeetingButtonDidTap), for: .touchUpInside @@ -147,58 +133,42 @@ class PromiseViewController: BaseViewController { // MARK: - Extension private extension PromiseViewController { - func setupBindings() { + func setupBinding() { viewModel.promiseInfo.bindOnMain(with: self) { owner, info in - owner.setupNavigationBarTitle(with: info?.promiseName ?? "", isBorderHidden: true) - owner.promiseInfoViewController.setupContent() - owner.promiseInfoViewController.setUpTimeContent() - owner.removePromiseViewContoller.promiseNameLabel.text = info?.promiseName ?? "" + guard let info else { return } - guard let isParticipant = info?.isParticipant else { return } + let moreButton = UIBarButtonItem( + image: .imgMore.withRenderingMode(.alwaysOriginal), + style: .plain, + target: owner, + action: #selector(owner.moreButtonDidTap) + ) - owner.setupPromiseEditButton(isHidden: !isParticipant) - owner.promiseInfoViewController.rootView.editButton.isHidden = !isParticipant + owner.navigationItem.rightBarButtonItem = info.isParticipant ? moreButton : nil + owner.removePromiseViewContoller.promiseNameLabel.text = info.promiseName + owner.setupNavigationBarTitle(with: info.promiseName, isBorderHidden: true) } - } - - func setupPromiseEditButton(isHidden: Bool) { - let moreButton = UIBarButtonItem( - image: .imgMore.withRenderingMode(.alwaysOriginal), - style: .plain, - target: self, - action: #selector(self.moreButtonDidTap) - ) - - navigationItem.rightBarButtonItem = isHidden ? nil : moreButton - } - - @objc - func didSegmentedControlIndexUpdated() { - let condition = viewModel.currentPageIndex.value <= promiseSegmentedControl.selectedSegmentIndex - let direction: UIPageViewController.NavigationDirection = condition ? .forward : .reverse - let (width, count, selectedIndex) = ( - promiseSegmentedControl.bounds.width, - promiseSegmentedControl.numberOfSegments, - promiseSegmentedControl.selectedSegmentIndex - ) - promiseSegmentedControl.selectedUnderLineView.snp.updateConstraints { - $0.leading.equalToSuperview().offset((width / CGFloat(count)) * CGFloat(selectedIndex)) + viewModel.currentPageIndex.bindOnMain(with: self) { owner, index in + let direction: UIPageViewController.NavigationDirection = owner.viewModel.pageControlDirection ? .forward : .reverse + let (width, count) = ( + owner.promiseSegmentedControl.bounds.width, + owner.promiseSegmentedControl.numberOfSegments + ) + + owner.promiseSegmentedControl.selectedUnderLineView.snp.updateConstraints { + $0.leading.equalToSuperview().offset((width / CGFloat(count)) * CGFloat(index)) + } + + owner.promisePageViewController.setViewControllers([ + owner.promiseViewControllerList[index] + ], direction: direction, animated: false) } - viewModel.segmentIndexDidChange( - index: promiseSegmentedControl.selectedSegmentIndex - ) - - promisePageViewController.setViewControllers([ - promiseViewControllerList[viewModel.currentPageIndex.value] - ], direction: direction, animated: false) - } - - @objc - func finishMeetingButtonDidTap() { - promiseTardyViewController.viewModel.updatePromiseCompletion { - DispatchQueue.main.async { + viewModel.isFinishSuccess.bindOnMain(with: self) { owner, isSuccess in + guard let isSuccess else { return } + + if isSuccess { self.navigationController?.popViewController(animated: true) if let viewController = self.navigationController?.viewControllers.last { @@ -207,6 +177,32 @@ private extension PromiseViewController { } } } + + viewModel.errorMessage.bindOnMain(with: self) { owner, message in + guard let message else { return } + let toast = Toast() + + toast.show(message: message, view: owner.view, position: .bottom, inset: 100) + } + + viewModel.isExitSuccess.bindOnMain(with: self) { owner, isSuccess in + owner.navigationController?.popViewController(animated: true) + } + + viewModel.isDeleteSuccess.bindOnMain(with: self) { owner, isSuccess in + owner.navigationController?.popViewController(animated: true) + } + } + + @objc + func didSegmentedControlIndexUpdated() { + viewModel.pageControlDirection = viewModel.currentPageIndex.value <= promiseSegmentedControl.selectedSegmentIndex + viewModel.currentPageIndex.value = promiseSegmentedControl.selectedSegmentIndex + } + + @objc + func finishMeetingButtonDidTap() { + viewModel.updatePromiseCompletion() } @objc @@ -238,22 +234,13 @@ private extension PromiseViewController { extension PromiseViewController: CustomActionSheetDelegate { func actionButtonDidTap(for kind: ActionSheetKind) { if kind == .deletePromise { - dismiss(animated: false) + viewModel.deletePromise() - viewModel.deletePromise() { - DispatchQueue.main.async { - self.navigationController?.popViewController(animated: true) - } - } - } - else { dismiss(animated: false) + } else { + viewModel.exitPromise() - viewModel.exitPromise() { - DispatchQueue.main.async { - self.navigationController?.popViewController(animated: true) - } - } + dismiss(animated: false) } } } diff --git a/KkuMulKum/Source/Promise/PromiseInfo/View/PromiseInfoView.swift b/KkuMulKum/Source/Promise/PromiseInfo/View/PromiseInfoView.swift index 04b4045f..c66ec431 100644 --- a/KkuMulKum/Source/Promise/PromiseInfo/View/PromiseInfoView.swift +++ b/KkuMulKum/Source/Promise/PromiseInfo/View/PromiseInfoView.swift @@ -28,9 +28,12 @@ class PromiseInfoView: BaseView { $0.backgroundColor = .white } + let participantLabel: UILabel = UILabel().then { + $0.setText("약속 참여 인원", style: .body05, color: .maincolor) + } + let participantNumberLabel: UILabel = UILabel().then { - $0.setText("약속 참여 인원 n명", style: .body05, color: .maincolor) - $0.setHighlightText("n명", style: .body05, color: .gray3) + $0.setText("n명", style: .body05, color: .gray3) } let participantCollectionView: UICollectionView = UICollectionView( @@ -68,7 +71,7 @@ class PromiseInfoView: BaseView { $0.layer.cornerRadius = Screen.height(18) } - private let locationInfoLabel: UILabel = UILabel().then { + let locationInfoLabel: UILabel = UILabel().then { $0.setText("위치", style: .body05, color: .maincolor) } @@ -77,7 +80,7 @@ class PromiseInfoView: BaseView { $0.layer.cornerRadius = Screen.height(8) } - private let timeInfoLabel: UILabel = UILabel().then { + let timeInfoLabel: UILabel = UILabel().then { $0.setText("약속시간", style: .body05, color: .maincolor) } @@ -86,7 +89,7 @@ class PromiseInfoView: BaseView { $0.layer.cornerRadius = Screen.height(8) } - private let readyLevelInfoLabel: UILabel = UILabel().then { + let readyLevelInfoLabel: UILabel = UILabel().then { $0.setText("꾸레벨", style: .body05, color: .maincolor) } @@ -95,7 +98,7 @@ class PromiseInfoView: BaseView { $0.layer.cornerRadius = Screen.height(8) } - private let penaltyLevelInfoLabel: UILabel = UILabel().then { + let penaltyLevelInfoLabel: UILabel = UILabel().then { $0.setText("벌칙", style: .body05, color: .maincolor) } @@ -117,6 +120,7 @@ class PromiseInfoView: BaseView { ) backgroundView.addSubviews( + participantLabel, participantNumberLabel, participantCollectionView, locationInfoLabel, @@ -165,11 +169,16 @@ class PromiseInfoView: BaseView { $0.bottom.equalToSuperview() } - participantNumberLabel.snp.makeConstraints { + participantLabel.snp.makeConstraints { $0.top.equalToSuperview().offset(18) $0.leading.equalToSuperview().offset(20) } + participantNumberLabel.snp.makeConstraints { + $0.centerY.equalTo(participantLabel) + $0.leading.equalTo(participantLabel.snp.trailing).offset(4) + } + participantCollectionView.snp.makeConstraints { $0.top.equalTo(participantNumberLabel.snp.bottom).offset(8.5) $0.horizontalEdges.equalToSuperview() diff --git a/KkuMulKum/Source/Promise/PromiseInfo/ViewController/PromiseInfoViewController.swift b/KkuMulKum/Source/Promise/PromiseInfo/ViewController/PromiseInfoViewController.swift index 44da0ec1..db107ae6 100644 --- a/KkuMulKum/Source/Promise/PromiseInfo/ViewController/PromiseInfoViewController.swift +++ b/KkuMulKum/Source/Promise/PromiseInfo/ViewController/PromiseInfoViewController.swift @@ -12,10 +12,10 @@ import Kingfisher class PromiseInfoViewController: BaseViewController { - // MARK: Property + // MARK: - Property - let rootView: PromiseInfoView = PromiseInfoView() - let viewModel: PromiseViewModel + private let viewModel: PromiseViewModel + private let rootView: PromiseInfoView = PromiseInfoView() // MARK: - LifeCycle @@ -43,9 +43,9 @@ class PromiseInfoViewController: BaseViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - viewModel.fetchPromiseParticipantList() - viewModel.fetchPromiseInfo() viewModel.fetchTardyInfo() + viewModel.fetchPromiseInfo() + viewModel.fetchPromiseParticipantList() } @@ -61,7 +61,11 @@ class PromiseInfoViewController: BaseViewController { } override func setupAction() { - rootView.editButton.addTarget(self, action: #selector(editButtonDidTap), for: .touchUpInside) + rootView.editButton.addTarget( + self, + action: #selector(editButtonDidTap), + for: .touchUpInside + ) } } @@ -69,125 +73,86 @@ class PromiseInfoViewController: BaseViewController { // MARK: - Extension extension PromiseInfoViewController { - @objc - func editButtonDidTap() { - if var dressUpLevel = viewModel.promiseInfo.value?.dressUpLevel { - let levels = ["LV1", "LV2", "LV3", "LV4", "FREE"] - - if dressUpLevel.contains("마음대로 입고 오기") { - dressUpLevel = "FREE" - } - else { - if let matched = levels.first(where: { - level in dressUpLevel.replacingOccurrences(of: " ", with: "").contains(level) - }) { - dressUpLevel = matched - } - } - - let viewController = EditPromiseViewController( - viewModel: EditPromiseViewModel( - promiseID: viewModel.promiseID, - promiseName: viewModel.promiseInfo.value?.promiseName, - placeName: viewModel.promiseInfo.value?.placeName, - xCoordinate: viewModel.promiseInfo.value?.x, - yCoordinate: viewModel.promiseInfo.value?.y, - address: viewModel.promiseInfo.value?.address, - roadAddress: viewModel.promiseInfo.value?.roadAddress, - time: viewModel.promiseInfo.value?.time, - dressUpLevel: dressUpLevel, - penalty: viewModel.promiseInfo.value?.penalty, - service: PromiseService() - ) - ) + func setupBinding() { + viewModel.promiseInfo.bindOnMain(with: self) { owner, info in + guard let info else { return } + let time = owner.viewModel.convertTime() - navigationController?.pushViewController(viewController, animated: true) + owner.rootView.editButton.isHidden = owner.viewModel.isEditButtonHidden() + owner.rootView.promiseNameLabel.setText(info.promiseName, style: .body01) + owner.rootView.locationContentLabel.setText(info.placeName, style: .body04) + owner.rootView.readyLevelContentLabel.setText(info.dressUpLevel, style: .body04) + owner.rootView.penaltyLevelContentLabel.setText(info.penalty, style: .body04) + owner.rootView.timeContentLabel.setText(time, style: .body04) } - } - - func setupBinding() { - viewModel.isPastDue.bindOnMain(with: self) { owner, isPastDue in - owner.rootView.editButton.isHidden = isPastDue + + viewModel.participantList.bindOnMain(with: self) { owner, list in + owner.rootView.participantNumberLabel.setText("\(list.count)명", style: .body05, color: .gray3) + owner.rootView.participantCollectionView.reloadData() } - viewModel.participantsInfo.bindOnMain(with: self) { owner, participantsInfo in - owner.rootView.participantNumberLabel.setText( - "약속 참여 인원 \(participantsInfo?.count ?? 0)명", - style: .body05, - color: .maincolor - ) - - owner.rootView.participantNumberLabel.setHighlightText( - "\(participantsInfo?.count ?? 0)명", - style: .body05, - color: .gray3 - ) + viewModel.dDay.bindOnMain(with: self) { owner, dDay in + guard let dDay else { return } - self.rootView.participantCollectionView.layoutIfNeeded() - owner.rootView.participantCollectionView.reloadData() + switch dDay { + case 1...: + owner.rootView.dDayLabel.setText("D-\(dDay)", style: .body05, color: .gray5) + case 0: + owner.rootView.dDayLabel.setText("D-DAY", style: .body05, color: .mainorange) + case ..<0: + owner.rootView.do { + $0.dDayLabel.setText("D+\(-dDay)", style: .body05, color: .gray4) + $0.promiseImageView.image = .imgPromiseGray + $0.participantLabel.textColor = .gray4 + $0.promiseNameLabel.textColor = .gray4 + $0.locationInfoLabel.textColor = .gray4 + $0.timeInfoLabel.textColor = .gray4 + $0.readyLevelInfoLabel.textColor = .gray4 + $0.penaltyLevelInfoLabel.textColor = .gray4 + } + default: + break + } } - } - - func setupContent() { - self.rootView.promiseNameLabel.setText( - self.viewModel.promiseInfo.value?.promiseName ?? "", - style: .body01 - ) - self.rootView.locationContentLabel.setText( - self.viewModel.promiseInfo.value?.placeName ?? "약속 장소 미설정", - style: .body04 - ) - self.rootView.readyLevelContentLabel.setText( - self.viewModel.promiseInfo.value?.dressUpLevel ?? "꾸레벨 미설정", - style: .body04 - ) - self.rootView.penaltyLevelContentLabel.setText( - self.viewModel.promiseInfo.value?.penalty ?? "벌칙 미설정", - style: .body04 - ) + viewModel.isPastDue.bindOnMain(with: self) { owner, _ in + owner.rootView.editButton.isHidden = owner.viewModel.isEditButtonHidden() + } } - func setUpTimeContent() { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - dateFormatter.locale = Locale(identifier: "ko_KR") - dateFormatter.timeZone = TimeZone(identifier: "Asia/Seoul") - - guard let dateWithTime = dateFormatter.date(from: viewModel.promiseInfo.value?.time ?? "") else { - return - } - - let calendar = Calendar.current - - let dateOnly = calendar.startOfDay(for: dateWithTime) - let today = calendar.startOfDay(for: Date()) - - let components = calendar.dateComponents([.day], from: today, to: dateOnly) - guard let remainDay = components.day else { - return - } - - if remainDay == 0 { - rootView.dDayLabel.setText("D-DAY", style: .body05, color: .mainorange) - rootView.promiseImageView.image = .imgPromise - rootView.promiseNameLabel.textColor = .gray7 - } else if remainDay < 0 { - rootView.dDayLabel.setText("D+\(-remainDay)", style: .body05, color: .gray4) - rootView.promiseImageView.image = .imgPromiseGray - rootView.promiseNameLabel.textColor = .gray4 + @objc + func editButtonDidTap() { + guard var dressUpLevel = viewModel.promiseInfo.value?.dressUpLevel else { return } + + let levels = ["LV1", "LV2", "LV3", "LV4", "FREE"] + + if dressUpLevel.contains("마음대로 입고 오기") { + dressUpLevel = "FREE" } else { - rootView.dDayLabel.setText("D-\(remainDay)", style: .body05, color: .gray5) - rootView.promiseImageView.image = .imgPromise - rootView.promiseNameLabel.textColor = .gray7 + if let matched = levels.first(where: { level in + dressUpLevel.replacingOccurrences(of: " ", with: "").contains(level) + }) { + dressUpLevel = matched + } } - - dateFormatter.dateFormat = "M월 d일 a h:mm" - dateFormatter.amSymbol = "AM" - dateFormatter.pmSymbol = "PM" - let time = dateFormatter.string(from: dateWithTime) - - rootView.timeContentLabel.setText(time, style: .body04) + + let viewController = EditPromiseViewController( + viewModel: EditPromiseViewModel( + promiseID: viewModel.promiseID, + promiseName: viewModel.promiseInfo.value?.promiseName, + placeName: viewModel.promiseInfo.value?.placeName, + xCoordinate: viewModel.promiseInfo.value?.x, + yCoordinate: viewModel.promiseInfo.value?.y, + address: viewModel.promiseInfo.value?.address, + roadAddress: viewModel.promiseInfo.value?.roadAddress, + time: viewModel.promiseInfo.value?.time, + dressUpLevel: dressUpLevel, + penalty: viewModel.promiseInfo.value?.penalty, + service: PromiseService() + ) + ) + + navigationController?.pushViewController(viewController, animated: true) } } @@ -199,11 +164,7 @@ extension PromiseInfoViewController: UICollectionViewDataSource { _ collectionView: UICollectionView, numberOfItemsInSection section: Int ) -> Int { - guard let info = viewModel.participantsInfo.value else { - return 0 - } - - return info.count + return viewModel.participantList.value.count } func collectionView( @@ -212,22 +173,18 @@ extension PromiseInfoViewController: UICollectionViewDataSource { ) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: ParticipantCollectionViewCell.reuseIdentifier, - for: indexPath) as? ParticipantCollectionViewCell - else { return UICollectionViewCell() } - - guard let info = viewModel.participantsInfo.value?[indexPath.row] else { - return cell + for: indexPath + ) as? ParticipantCollectionViewCell else { + return UICollectionViewCell() } - cell.userNameLabel.setText(info.name, style: .caption02, color: .gray6) + let info = viewModel.participantList.value[indexPath.row] - guard let image = URL(string: info.profileImageURL ?? "") else { - cell.profileImageView.image = .imgProfile - - return cell - } - - cell.profileImageView.kf.setImage(with: image) + cell.userNameLabel.setText(info.name, style: .caption02, color: .gray6) + cell.profileImageView.kf.setImage( + with: URL(string: info.profileImageURL ?? ""), + placeholder: UIImage.imgProfile + ) return cell } @@ -237,7 +194,11 @@ extension PromiseInfoViewController: UICollectionViewDataSource { // MARK: - UICollectionViewDelegateFlowLayout extension PromiseInfoViewController: UICollectionViewDelegateFlowLayout { - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + func collectionView( + _ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath + ) -> CGSize { return CGSize(width: Screen.width(68), height: Screen.height(88)) } } diff --git a/KkuMulKum/Source/Promise/ReadyStatus/Cell/OurReadyStatusCollectionViewCell.swift b/KkuMulKum/Source/Promise/ReadyStatus/Cell/OurReadyStatusCollectionViewCell.swift index 72609de5..9b924423 100644 --- a/KkuMulKum/Source/Promise/ReadyStatus/Cell/OurReadyStatusCollectionViewCell.swift +++ b/KkuMulKum/Source/Promise/ReadyStatus/Cell/OurReadyStatusCollectionViewCell.swift @@ -26,6 +26,12 @@ class OurReadyStatusCollectionViewCell: BaseCollectionViewCell { $0.layer.borderWidth = 0.5 } + override func prepareForReuse() { + super.prepareForReuse() + + profileImageView.image = nil + } + override func setupView() { backgroundColor = .gray0 layer.cornerRadius = Screen.height(8) diff --git a/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyPlanInfoView.swift b/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyPlanInfoView.swift index 914ffe01..294a39c4 100644 --- a/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyPlanInfoView.swift +++ b/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyPlanInfoView.swift @@ -13,16 +13,16 @@ class ReadyPlanInfoView: BaseView { // MARK: Property let readyTimeLabel: UILabel = UILabel().then { - $0.setText("12시 30분에 준비하고,\n1시에 이동을 시작해야 해요", style: .body03) - $0.setHighlightText("12시 30분", "1시", style: .body03, color: .maincolor) + $0.setText("--시 --분에 준비하고,\n-시에 이동을 시작해야 해요", style: .body03) + $0.setHighlightText("--시 --분", "-시", style: .body03, color: .maincolor) } let requestReadyTimeLabel: UILabel = UILabel().then { - $0.setText("준비 소요 시간: 30분", style: .label02, color: .gray8) + $0.setText("준비 소요 시간: --분", style: .label02, color: .gray8) } let requestMoveTimeLabel: UILabel = UILabel().then { - $0.setText("이동 소요 시간: 1시간 30분", style: .label02, color: .gray8) + $0.setText("이동 소요 시간: --분", style: .label02, color: .gray8) } let editButton: UIButton = UIButton().then { diff --git a/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyStatusButton.swift b/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyStatusButton.swift index 28cbf2c1..716de784 100644 --- a/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyStatusButton.swift +++ b/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyStatusButton.swift @@ -7,7 +7,7 @@ import UIKit -enum ReadyProgressStatus { +enum ReadyStatus { case none case ready case move @@ -19,7 +19,7 @@ class ReadyStatusButton: UIButton { // MARK: - LifeCycle - init(title: String, readyStatus: ReadyProgressStatus) { + init(title: String, readyStatus: ReadyStatus) { super.init(frame: .zero) setupButton(title, readyStatus) @@ -37,7 +37,7 @@ class ReadyStatusButton: UIButton { extension ReadyStatusButton { func setupButton( _ title: String, - _ readyStatus: ReadyProgressStatus + _ readyStatus: ReadyStatus ) { switch readyStatus { case .none: diff --git a/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyStatusProgressView.swift b/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyStatusProgressView.swift index 5e436ba5..31c04b1b 100644 --- a/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyStatusProgressView.swift +++ b/KkuMulKum/Source/Promise/ReadyStatus/View/ReadyStatusProgressView.swift @@ -171,7 +171,7 @@ class ReadyStatusProgressView: BaseView { } arrivalCheckImageView.snp.makeConstraints { - $0.centerX.equalTo(arrivalTimeLabel) + $0.trailing.equalToSuperview().inset(53.5) $0.centerY.equalTo(readyStartCheckImageView) $0.height.equalTo(Screen.height(16)) $0.width.equalTo(arrivalCheckImageView.snp.height) diff --git a/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift b/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift index 65661298..61c2e4f5 100644 --- a/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift +++ b/KkuMulKum/Source/Promise/ReadyStatus/ViewController/ReadyStatusViewController.swift @@ -98,441 +98,182 @@ class ReadyStatusViewController: BaseViewController { extension ReadyStatusViewController { func setupBinding() { - viewModel.isPastDue.bindOnMain(with: self) { owner, flag in - guard let isParticipant = owner.viewModel.promiseInfo.value?.isParticipant else { return } - - owner.rootView.readyPlanInfoView.editButton.isHidden = flag - owner.rootView.enterReadyButtonView.isUserInteractionEnabled = !flag && isParticipant - } - - viewModel.promiseInfo.bindOnMain(with: self) { owner, model in - guard let isParticipant = model?.isParticipant else { return } - - if !isParticipant { - owner.updateReadyInfoView(flag: false) - owner.rootView.myReadyStatusProgressView.readyStartButton.setupButton("준비 시작", .none) - } + viewModel.participantList.bindOnMain(with: self) { owner, _ in + owner.rootView.ourReadyStatusCollectionView.reloadData() } - viewModel.myReadyStatus.bindOnMain(with: self) { owner, model in - guard let status = model else { return } - - /// 준비 시간을 계산해 UI에 표시 - owner.viewModel.calculateDuration() - owner.viewModel.calculateStartTime() - - /// myReadyStatus의 바인딩 부분에 조건을 통해 myReadyProgressStatus 값을 업데이트 - if status.preparationStartAt == nil { - owner.viewModel.myReadyProgressStatus.value = .none - } - else if status.departureAt == nil { - owner.viewModel.myReadyProgressStatus.value = .ready - } - else if status.arrivalAt == nil { - owner.viewModel.myReadyProgressStatus.value = .move - } - else { - owner.viewModel.myReadyProgressStatus.value = .done - } - - /// 준비하기 버튼과 준비 정보 화면 중 어떤 걸 표시할지 결정 - if status.preparationTime == nil { - owner.updateReadyInfoView(flag: false) - return - } + viewModel.isPastDue.bindOnMain(with: self) { owner, isPastDue in + guard let isPastDue else { return } - owner.updateReadyInfoView(flag: true) - owner.updateLatePopupStatus() + owner.rootView.readyPlanInfoView.editButton.isHidden = isPastDue } - viewModel.myReadyProgressStatus.bindOnMain(with: self) { owner, status in - owner.updateLatePopupStatus() - } - - viewModel.moveDuration.bind(with: self) { owner, moveTime in - owner.rootView.readyPlanInfoView.requestMoveTimeLabel.setText( - "이동 소요 시간: \(moveTime)", - style: .label02, - color: .gray8 - ) - } - - viewModel.readyDuration.bind(with: self) { - owner, - readyTime in - owner.rootView.readyPlanInfoView.requestReadyTimeLabel.setText( - "준비 소요 시간: \(readyTime)", - style: .label02, - color: .gray8 - ) - } - - viewModel.readyStartTime.bind(with: self) { - owner, - readyStartTime in - DispatchQueue.main.async { - owner.rootView.readyPlanInfoView.readyTimeLabel.setText( - "\(readyStartTime)에 준비하고,\n\(owner.viewModel.moveStartTime.value)에 이동을 시작해야 해요", - style: .body03 - ) - - owner.rootView.readyPlanInfoView.readyTimeLabel.setHighlightText( - for: [readyStartTime, owner.viewModel.moveStartTime.value], - style: .body03, - color: .maincolor - ) + viewModel.promiseInfo.bindOnMain(with: self) { owner, info in + guard let promiseInfo = info else { return } + let isParticipant = promiseInfo.isParticipant + + owner.rootView.do { + $0.enterReadyButtonView.isHidden = owner.viewModel.isReadyInfoEntered() + $0.readyPlanInfoView.isHidden = !$0.enterReadyButtonView.isHidden + $0.enterReadyButtonView.isUserInteractionEnabled = isParticipant + $0.myReadyStatusProgressView.isUserInteractionEnabled = isParticipant } } - viewModel.moveStartTime.bind(with: self) { owner, moveStartTime in - DispatchQueue.main.async { - owner.rootView.readyPlanInfoView.readyTimeLabel.setText( - "\(owner.viewModel.readyStartTime.value)에 준비하고,\n\(moveStartTime)에 이동을 시작해야 해요", - style: .body03 - ) - - owner.rootView.readyPlanInfoView.readyTimeLabel.setHighlightText( - for: [owner.viewModel.readyStartTime.value, moveStartTime], - style: .body03, - color: .maincolor - ) + viewModel.myReadyInfo.bindOnMain(with: self) { owner, status in + let preparationStartAt = status?.preparationStartAt ?? " " + let departureAt = status?.departureAt ?? " " + let arrivalAt = status?.arrivalAt ?? " " + + owner.rootView.myReadyStatusProgressView.do { + $0.readyStartTimeLabel.text = preparationStartAt + $0.moveStartTimeLabel.text = departureAt + $0.arrivalTimeLabel.text = arrivalAt } - } - - viewModel.myReadyProgressStatus.bindOnMain(with: self) { owner, status in - owner.updateReadyStartButton() - owner.updateLatePopupStatus() - owner.rootView.ourReadyStatusCollectionView.reloadData() - } - - viewModel.participantsInfo.bindOnMain(with: self) { owner, participants in - owner.rootView.ourReadyStatusCollectionView.reloadData() - owner.rootView.ourReadyStatusCollectionView.snp.updateConstraints { - $0.height.equalTo( - CGFloat(participants?.count ?? 0) * Screen.height(80) - ) + owner.rootView.do { + $0.enterReadyButtonView.isHidden = owner.viewModel.isReadyInfoEntered() + $0.readyPlanInfoView.isHidden = !$0.enterReadyButtonView.isHidden } } - } - - func updateLatePopupStatus() { - let date = Calendar.current - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "HH시 mm분" - dateFormatter.locale = Locale(identifier: "ko_KR") - dateFormatter.timeZone = TimeZone(identifier: "Asia/Seoul") - - guard let readyStartTime = dateFormatter.date(from: viewModel.readyStartTime.value) else { return } - guard let moveStartTime = dateFormatter.date(from: viewModel.moveStartTime.value) else { return } - - let readyStartComponent = date.dateComponents([.hour, .minute], from: readyStartTime) - let moveStartComponents = date.dateComponents([.hour, .minute], from: moveStartTime) - let currentTimeComponents = date.dateComponents([.hour, .minute], from: Date()) - - guard let readyStartHour = readyStartComponent.hour, - let readyStartMinute = readyStartComponent.minute, - let moveStartHour = moveStartComponents.hour, - let moveStartMinute = moveStartComponents.minute, - let currentTimeHour = currentTimeComponents.hour, - let currentTimeMinute = currentTimeComponents.minute else { return } - DispatchQueue.main.async { - if (currentTimeHour >= readyStartHour) && (currentTimeMinute > readyStartMinute) && ((currentTimeHour <= moveStartHour) && (currentTimeMinute < moveStartMinute)) { - let readyLateFlag = !(self.viewModel.myReadyProgressStatus.value == .none) - - print(">> \(readyLateFlag) : \(#function)") - - self.rootView.popUpImageView.isHidden = readyLateFlag - } - else if ((currentTimeHour >= moveStartHour) && (currentTimeMinute > moveStartMinute)) && self.viewModel.isPastDue.value == false { - let moveLateFlag = !(self.viewModel.myReadyProgressStatus.value == .none || self.viewModel.myReadyProgressStatus.value == .ready) - - print(">>> \(moveLateFlag) : \(#function)") - - self.rootView.popUpImageView.isHidden = moveLateFlag - } - else { - let arriveLateFlag = self.viewModel.isPastDue.value && !(self.viewModel.myReadyProgressStatus.value == .done) - - print(">>>> \(arriveLateFlag) : \(#function)") - - self.rootView.popUpImageView.isHidden = arriveLateFlag + viewModel.myReadyStatus.bindOnMain(with: self) { owner, state in + owner.rootView.myReadyStatusProgressView.do { + switch state { + case .none: + $0.readyStartButton.setupButton("준비 시작", .ready) + $0.moveStartButton.setupButton("이동 시작", .none) + $0.arrivalButton.setupButton("도착 완료", .none) + $0.readyStartButton.isEnabled = true + $0.moveStartButton.isEnabled = false + $0.arrivalButton.isEnabled = false + $0.readyStartTitleLabel.isHidden = false + $0.moveStartTitleLabel.isHidden = true + $0.arrivalTitleLabel.isHidden = true + $0.readyStartTimeLabel.isHidden = true + $0.moveStartTimeLabel.isHidden = true + $0.arrivalTimeLabel.isHidden = true + $0.statusProgressView.setProgress(0.0, animated: false) + case .ready: + $0.readyStartButton.setupButton("준비 중", .move) + $0.moveStartButton.setupButton("이동 시작", .ready) + $0.arrivalButton.setupButton("도착 완료", .none) + $0.readyStartButton.isEnabled = false + $0.moveStartButton.isEnabled = true + $0.arrivalButton.isEnabled = false + $0.readyStartTitleLabel.isHidden = true + $0.moveStartTitleLabel.isHidden = false + $0.arrivalTitleLabel.isHidden = true + $0.readyStartTimeLabel.isHidden = false + $0.moveStartTimeLabel.isHidden = true + $0.arrivalTimeLabel.isHidden = true + $0.readyStartCheckImageView.image = .iconCheck + $0.statusProgressView.setProgress(0.2, animated: false) + case .move: + $0.readyStartButton.setupButton("준비 완료", .done) + $0.moveStartButton.setupButton("이동 중", .move) + $0.arrivalButton.setupButton("도착 완료", .ready) + $0.readyStartButton.isEnabled = false + $0.moveStartButton.isEnabled = false + $0.arrivalButton.isEnabled = true + $0.readyStartTitleLabel.isHidden = true + $0.moveStartTitleLabel.isHidden = true + $0.arrivalTitleLabel.isHidden = false + $0.readyStartTimeLabel.isHidden = false + $0.moveStartTimeLabel.isHidden = false + $0.arrivalTimeLabel.isHidden = true + $0.readyStartCheckImageView.image = .iconCheck + $0.moveStartCheckImageView.image = .iconCheck + $0.statusProgressView.setProgress(0.5, animated: false) + case .done: + $0.readyStartButton.setupButton("준비 완료", .done) + $0.moveStartButton.setupButton("이동 완료", .done) + $0.arrivalButton.setupButton("도착 완료", .done) + $0.readyStartButton.isEnabled = false + $0.moveStartButton.isEnabled = false + $0.arrivalButton.isEnabled = false + $0.readyStartTitleLabel.isHidden = true + $0.moveStartTitleLabel.isHidden = true + $0.arrivalTitleLabel.isHidden = true + $0.readyStartTimeLabel.isHidden = false + $0.moveStartTimeLabel.isHidden = false + $0.arrivalTimeLabel.isHidden = false + $0.readyStartCheckImageView.image = .iconCheck + $0.moveStartCheckImageView.image = .iconCheck + $0.arrivalCheckImageView.image = .iconCheck + $0.statusProgressView.setProgress(1, animated: false) + } } } - } - - /// flag에 따라 준비 정보 입력 버튼 표시 유무 변경 - func updateReadyInfoView(flag: Bool) { - rootView.enterReadyButtonView.isHidden = flag - rootView.readyPlanInfoView.isHidden = !flag - } - - /// 준비 시작이나 이동 시작 시간이 늦었을 때 팝업 표시 여부 변경 - func updatePopUpImageView() { - rootView.popUpImageView.isHidden = !viewModel.isLate.value - rootView.readyBaseView.layoutIfNeeded() - } - - /// 준비 상태에 따라 버튼 상태 변경 - func updateReadyStartButton() { - switch viewModel.myReadyProgressStatus.value { - case .none: - DispatchQueue.main.async { - self.rootView.myReadyStatusProgressView.readyStartButton.setupButton( - "준비 시작", - .ready - ) - self.rootView.myReadyStatusProgressView.moveStartButton.setupButton( - "이동 시작", - .none - ) - self.rootView.myReadyStatusProgressView.arrivalButton.setupButton( - "도착 완료", - .none - ) - self.rootView.myReadyStatusProgressView.statusProgressView.setProgress(0, animated: false) - } - case .ready: - DispatchQueue.main.async { - self.rootView.myReadyStatusProgressView.readyStartButton.setupButton( - "준비 중", - .move - ) - self.rootView.myReadyStatusProgressView.moveStartButton.setupButton( - "이동 시작", - .ready - ) - self.rootView.myReadyStatusProgressView.arrivalButton.setupButton( - "도착 완료", - .none - ) - self.rootView.myReadyStatusProgressView.statusProgressView.setProgress(0.2, animated: false) - - self.rootView.myReadyStatusProgressView.readyStartButton.isEnabled = false - self.rootView.myReadyStatusProgressView.moveStartButton.isEnabled = true - - [ - self.rootView.myReadyStatusProgressView.moveStartTimeLabel, - self.rootView.myReadyStatusProgressView.readyStartTitleLabel - ].forEach { $0.isHidden = true } - - [ - self.rootView.myReadyStatusProgressView.readyStartTimeLabel, - self.rootView.myReadyStatusProgressView.moveStartTitleLabel - ].forEach { $0.isHidden = false } - - self.rootView.myReadyStatusProgressView.readyStartCheckImageView.backgroundColor = .green2 - - /// myReadyStatus의 preparationStartAt 값이 있으면 그 값으로 업데이트 - if let preparationStartAt = self.viewModel.myReadyStatus.value?.preparationStartAt { - self.rootView.myReadyStatusProgressView.readyStartTimeLabel.setText( - preparationStartAt, - style: .caption02, - color: .gray8 - ) - } - else { - self.rootView.myReadyStatusProgressView.readyStartTimeLabel.setText( - self.viewModel.updateReadyStatusTime(), - style: .caption02, - color: .gray8 - ) - } - } - case .move: - DispatchQueue.main.async { - self.rootView.myReadyStatusProgressView.readyStartButton.setupButton( - "준비 완료", - .done - ) - self.rootView.myReadyStatusProgressView.moveStartButton.setupButton( - "이동 중", - .move - ) - self.rootView.myReadyStatusProgressView.arrivalButton.setupButton( - "도착 완료", - .ready - ) - self.rootView.myReadyStatusProgressView.statusProgressView.setProgress( - 0.5, - animated: false - ) - - self.rootView.myReadyStatusProgressView.moveStartButton.isEnabled = false - self.rootView.myReadyStatusProgressView.arrivalButton.isEnabled = true - - self.rootView.myReadyStatusProgressView.arrivalTitleLabel.isHidden = false - self.rootView.myReadyStatusProgressView.moveStartTitleLabel.isHidden = true - self.rootView.myReadyStatusProgressView.readyStartTitleLabel.isHidden = true - self.rootView.myReadyStatusProgressView.arrivalTimeLabel.isHidden = true - self.rootView.myReadyStatusProgressView.moveStartTimeLabel.isHidden = false - self.rootView.myReadyStatusProgressView.readyStartTimeLabel.isHidden = false - - - self.rootView.myReadyStatusProgressView.readyStartCheckImageView.image = .iconCheck - self.rootView.myReadyStatusProgressView.moveStartCheckImageView.image = .iconCheck - - /// myReadyStatus의 arrivalAt 값이 있으면 그 값으로 업데이트 - if let preparationStartAt = self.viewModel.myReadyStatus.value?.preparationStartAt { - self.rootView.myReadyStatusProgressView.readyStartTimeLabel.setText( - preparationStartAt, - style: .caption02, - color: .gray8 - ) - } - if let departureAt = self.viewModel.myReadyStatus.value?.departureAt { - self.rootView.myReadyStatusProgressView.moveStartTimeLabel.setText( - departureAt, - style: .caption02, - color: .gray8 - ) - } - else { - self.rootView.myReadyStatusProgressView.moveStartTimeLabel.setText( - self.viewModel.updateReadyStatusTime(), - style: .caption02, - color: .gray8 - ) - } - } - case .done: - DispatchQueue.main.async { - self.rootView.myReadyStatusProgressView.readyStartButton.setupButton( - "준비 완료", - .done - ) - self.rootView.myReadyStatusProgressView.moveStartButton.setupButton( - "이동 완료", - .done - ) - self.rootView.myReadyStatusProgressView.arrivalButton.setupButton( - "도착 완료", - .done - ) - self.rootView.myReadyStatusProgressView.statusProgressView.setProgress( - 1, - animated: false - ) - - self.rootView.myReadyStatusProgressView.arrivalButton.isEnabled = false - - [ - self.rootView.myReadyStatusProgressView.arrivalTitleLabel, - self.rootView.myReadyStatusProgressView.moveStartTitleLabel, - self.rootView.myReadyStatusProgressView.readyStartTitleLabel - ].forEach { $0.isHidden = true } - - [ - self.rootView.myReadyStatusProgressView.arrivalTimeLabel, - self.rootView.myReadyStatusProgressView.moveStartTimeLabel, - self.rootView.myReadyStatusProgressView.readyStartTimeLabel - ].forEach { $0.isHidden = false } - - [ - self.rootView.myReadyStatusProgressView.readyStartCheckImageView, - self.rootView.myReadyStatusProgressView.moveStartCheckImageView, - self.rootView.myReadyStatusProgressView.arrivalCheckImageView - ].forEach { $0.image = .iconCheck } - - /// myReadyStatus의 arrivalAt 값이 있으면 그 값으로 업데이트 - if let preparationStartAt = self.viewModel.myReadyStatus.value?.preparationStartAt { - self.rootView.myReadyStatusProgressView.readyStartTimeLabel.setText( - preparationStartAt, - style: .caption02, - color: .gray8 - ) - } - if let departureAt = self.viewModel.myReadyStatus.value?.departureAt { - self.rootView.myReadyStatusProgressView.moveStartTimeLabel.setText( - departureAt, - style: .caption02, - color: .gray8 - ) - } - if let arrivalAt = self.viewModel.myReadyStatus.value?.arrivalAt { - self.rootView.myReadyStatusProgressView.arrivalTimeLabel.setText( - arrivalAt, - style: .caption02, - color: .gray8 - ) - } - else { - self.rootView.myReadyStatusProgressView.arrivalTimeLabel.setText( - self.viewModel.updateReadyStatusTime(), - style: .caption02, - color: .gray8 - ) - } + viewModel.requestReadyTime.bindOnMain(with: self) { owner, time in + owner.rootView.do { + $0.readyPlanInfoView.readyTimeLabel.setText("\(time[0])에 준비하고,\n\(time[1])에 이동을 시작해야 해요", style: .body03) + $0.readyPlanInfoView.readyTimeLabel.setHighlightText("\(time[0])","\(time[1])", style: .body03, color: .maincolor) + $0.readyPlanInfoView.requestReadyTimeLabel.setText("준비 소요 시간: \(time[2])", style: .label02, color: .gray8) + $0.readyPlanInfoView.requestMoveTimeLabel.setText("이동 소요 시간: \(time[3])", style: .label02, color: .gray8) } } } @objc func readyStartButtonDidTap() { - viewModel.updatePreparationStatus { [weak self] in - self?.viewModel.fetchPromiseParticipantList() - } + viewModel.updatePreparationStatus() } @objc func moveStartButtonDidTap() { - viewModel.updateDepartureStatus { [weak self] in - self?.viewModel.fetchPromiseParticipantList() - } + viewModel.updateDepartureStatus() } @objc func arrivalButtonDidTap() { - viewModel.updateArrivalStatus { [weak self] in - self?.viewModel.fetchPromiseParticipantList() - } + viewModel.updateArrivalStatus() } @objc func editReadyButtonDidTap() { - guard let _ = viewModel.promiseInfo.value?.promiseName else { return } - guard let readyStatusInfo = viewModel.myReadyStatus.value else { return } + guard let promiseName = viewModel.promiseInfo.value?.promiseName, + let promiseTime = viewModel.myReadyInfo.value?.promiseTime, + let preparationTime = viewModel.myReadyInfo.value?.preparationTime, + let travelTime = viewModel.myReadyInfo.value?.travelTime else { + return + } - let setReadyInfoViewModel = SetReadyInfoViewModel( + let viewModel = SetReadyInfoViewModel( promiseID: viewModel.promiseID, - promiseTime: readyStatusInfo.promiseTime, - promiseName: viewModel.promiseInfo.value?.promiseName ?? "", + promiseTime: promiseTime, + promiseName: promiseName, service: PromiseService() ) - setReadyInfoViewModel.storedReadyHour = (readyStatusInfo.preparationTime ?? 0) / 60 - setReadyInfoViewModel.storedReadyMinute = (readyStatusInfo.preparationTime ?? 0) % 60 - setReadyInfoViewModel.storedMoveHour = (readyStatusInfo.travelTime ?? 0) / 60 - setReadyInfoViewModel.storedMoveMinute = (readyStatusInfo.travelTime ?? 0) % 60 + viewModel.storedReadyHour = preparationTime / 60 + viewModel.storedReadyMinute = preparationTime % 60 + viewModel.storedMoveHour = travelTime / 60 + viewModel.storedMoveMinute = travelTime % 60 - let setReadyInfoViewController = SetReadyInfoViewController(viewModel: setReadyInfoViewModel) + let viewController = SetReadyInfoViewController(viewModel: viewModel) - navigationController?.pushViewController( - setReadyInfoViewController, - animated: true - ) + navigationController?.pushViewController(viewController, animated: true) } @objc func enterReadyButtonDidTap() { - guard let _ = viewModel.promiseInfo.value?.promiseName else { return } - guard let readyStatusInfo = viewModel.myReadyStatus.value else { return } + guard let promiseName = viewModel.promiseInfo.value?.promiseName, + let readyInfo = viewModel.myReadyInfo.value else { + return + } - let setReadyInfoViewController = SetReadyInfoViewController( + let viewController = SetReadyInfoViewController( viewModel: SetReadyInfoViewModel( promiseID: viewModel.promiseID, - promiseTime: readyStatusInfo.promiseTime, - promiseName: viewModel.promiseInfo.value?.promiseName ?? "", + promiseTime: readyInfo.promiseTime, + promiseName: promiseName, service: PromiseService() ) ) - navigationController?.pushViewController( - setReadyInfoViewController, - animated: true - ) + navigationController?.pushViewController(viewController, animated: true) } } @@ -544,7 +285,7 @@ extension ReadyStatusViewController: UICollectionViewDataSource { _ collectionView: UICollectionView, numberOfItemsInSection section: Int ) -> Int { - return viewModel.participantsInfo.value?.count ?? 0 + return viewModel.participantList.value.count } func collectionView( @@ -555,19 +296,13 @@ extension ReadyStatusViewController: UICollectionViewDataSource { withReuseIdentifier: OurReadyStatusCollectionViewCell.reuseIdentifier, for: indexPath ) as? OurReadyStatusCollectionViewCell - else { return UICollectionViewCell() } - - cell.nameLabel.setText( - viewModel.participantsInfo.value?[indexPath.row].name ?? "", - style: .body03, - color: .gray8 - ) - - if let imageURL = URL(string: viewModel.participantsInfo.value?[indexPath.row].profileImageURL ?? "") { - cell.profileImageView.kf.setImage(with: imageURL, placeholder: UIImage.imgProfile) + else { + return UICollectionViewCell() } - switch viewModel.participantsInfo.value?[indexPath.row].state { + let info = viewModel.participantList.value[indexPath.row] + + switch info.state { case "꾸물중": cell.readyStatusButton.setupButton("꾸물중", .none) case "준비중": @@ -576,12 +311,16 @@ extension ReadyStatusViewController: UICollectionViewDataSource { cell.readyStatusButton.setupButton("이동중", .move) case "도착": cell.readyStatusButton.setupButton("도착", .done) - case .none: - return cell - case .some(_): - return cell + default: + break } + cell.nameLabel.text = info.name + cell.profileImageView.kf.setImage( + with: URL(string: info.profileImageURL ?? ""), + placeholder: UIImage.imgProfile + ) + return cell } } diff --git a/KkuMulKum/Source/Promise/Tardy/View/ArriveView.swift b/KkuMulKum/Source/Promise/Tardy/View/NoTardyView.swift similarity index 71% rename from KkuMulKum/Source/Promise/Tardy/View/ArriveView.swift rename to KkuMulKum/Source/Promise/Tardy/View/NoTardyView.swift index 919e3daf..b7c7d2a3 100644 --- a/KkuMulKum/Source/Promise/Tardy/View/ArriveView.swift +++ b/KkuMulKum/Source/Promise/Tardy/View/NoTardyView.swift @@ -1,5 +1,5 @@ // -// ArriveView.swift +// NoTardyView.swift // KkuMulKum // // Created by YOUJIM on 7/14/24. @@ -7,18 +7,11 @@ import UIKit -class ArriveView: BaseView { +class NoTardyView: BaseView { // MARK: Property - let finishMeetingButton: CustomButton = CustomButton( - title: "약속 마치기", - isEnabled: true - ).then { - $0.backgroundColor = .maincolor - } - private let giftImageView: UIImageView = UIImageView().then { $0.image = .imgGift $0.contentMode = .scaleAspectFit @@ -41,8 +34,7 @@ class ArriveView: BaseView { addSubviews( giftImageView, mainTitleLabel, - subTitleLabel, - finishMeetingButton + subTitleLabel ) } @@ -63,12 +55,5 @@ class ArriveView: BaseView { $0.top.equalTo(mainTitleLabel.snp.bottom).offset(4) $0.centerX.equalToSuperview() } - - finishMeetingButton.snp.makeConstraints { - $0.bottom.equalToSuperview().inset(64) - $0.centerX.equalToSuperview() - $0.height.equalTo(CustomButton.defaultHeight) - $0.width.equalTo(CustomButton.defaultWidth) - } } } diff --git a/KkuMulKum/Source/Promise/Tardy/View/TardyView.swift b/KkuMulKum/Source/Promise/Tardy/View/TardyView.swift index 22ec581f..8c20583b 100644 --- a/KkuMulKum/Source/Promise/Tardy/View/TardyView.swift +++ b/KkuMulKum/Source/Promise/Tardy/View/TardyView.swift @@ -18,6 +18,8 @@ class TardyView: BaseView { let tardyEmptyView: TardyEmptyView = TardyEmptyView() + let noTardyView: NoTardyView = NoTardyView() + let tardyCollectionView: UICollectionView = UICollectionView( frame: .zero, collectionViewLayout: UICollectionViewFlowLayout().then { @@ -40,7 +42,7 @@ class TardyView: BaseView { $0.backgroundColor = .maincolor } - private let titleLabel: UILabel = UILabel().then { + let titleLabel: UILabel = UILabel().then { $0.setText("이번 약속의 지각 꾸물이는?", style: .head01, color: .gray8) } @@ -51,11 +53,12 @@ class TardyView: BaseView { backgroundColor = .white addSubviews( - tardyPenaltyView, titleLabel, tardyEmptyView, + noTardyView, tardyCollectionView, - finishMeetingButton + finishMeetingButton, + tardyPenaltyView ) } @@ -78,6 +81,10 @@ class TardyView: BaseView { $0.width.equalTo(Screen.width(112)) } + noTardyView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + finishMeetingButton.snp.makeConstraints { $0.bottom.equalToSuperview().inset(64) $0.centerX.equalToSuperview() diff --git a/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift b/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift index 653212a1..3c3ce373 100644 --- a/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift +++ b/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift @@ -13,8 +13,7 @@ class TardyViewController: BaseViewController { // MARK: Property let viewModel: PromiseViewModel - let tardyView: TardyView = TardyView() - let arriveView: ArriveView = ArriveView() + let rootView: TardyView = TardyView() // MARK: - LifeCycle @@ -29,6 +28,10 @@ class TardyViewController: BaseViewController { fatalError("init(coder:) has not been implemented") } + override func loadView() { + view = rootView + } + override func viewDidLoad() { super.viewDidLoad() @@ -44,18 +47,8 @@ class TardyViewController: BaseViewController { // MARK: - Setup - override func setupView() { - view.addSubviews(arriveView, tardyView) - - [arriveView, tardyView].forEach { - $0.snp.makeConstraints { - $0.edges.equalToSuperview() - } - } - } - override func setupDelegate() { - tardyView.tardyCollectionView.dataSource = self + rootView.tardyCollectionView.dataSource = self } } @@ -64,43 +57,76 @@ class TardyViewController: BaseViewController { private extension TardyViewController { func setupBinding() { - /// 시간이 지나고 지각자가 없을 때 arriveView로 띄워짐 - viewModel.isPastDue.bindOnMain(with: self) { owner, isPastDue in - owner.tardyView.finishMeetingButton.isEnabled = (isPastDue && (owner.viewModel.promiseInfo.value?.isParticipant ?? false)) - } - viewModel.penalty.bindOnMain(with: self) { owner, penalty in - owner.tardyView.tardyPenaltyView.contentLabel.setText( - penalty, - style: .body03, - color: .gray8 - ) - } - - viewModel.hasTardy.bindOnMain(with: self) { owner, hasTardy in - let isPastDue = owner.viewModel.isPastDue.value - let arriveHideFlag = !(isPastDue && !hasTardy) - - owner.arriveView.isHidden = arriveHideFlag - owner.tardyView.isHidden = !arriveHideFlag - owner.tardyView.tardyCollectionView.isHidden = !isPastDue - owner.tardyView.tardyEmptyView.isHidden = isPastDue + owner.rootView.tardyPenaltyView.contentLabel.text = penalty } - viewModel.comers.bind(with: self) { owner, comers in - DispatchQueue.main.async { - owner.tardyView.tardyCollectionView.reloadData() + viewModel.isPastDue.bindOnMain(with: self) { owner, isPastDue in + switch owner.viewModel.showTardyScreen() { + case .tardyEmptyView: + owner.rootView.do { + $0.finishMeetingButton.isEnabled = false + $0.tardyEmptyView.isHidden = false + $0.titleLabel.isHidden = false + $0.tardyPenaltyView.isHidden = false + $0.noTardyView.isHidden = true + $0.tardyCollectionView.isHidden = true + } + case .tardyListView: + owner.rootView.do { + $0.finishMeetingButton.isEnabled = true + $0.titleLabel.isHidden = false + $0.tardyPenaltyView.isHidden = false + $0.tardyCollectionView.isHidden = false + $0.tardyEmptyView.isHidden = true + $0.noTardyView.isHidden = true + + $0.tardyCollectionView.reloadData() + } + case .noTardyView: + owner.rootView.do { + $0.finishMeetingButton.isEnabled = true + $0.noTardyView.isHidden = false + $0.tardyEmptyView.isHidden = true + $0.titleLabel.isHidden = true + $0.tardyPenaltyView.isHidden = true + $0.tardyCollectionView.isHidden = true + } } } - viewModel.errorMessage.bindOnMain(with: self) { owner, error in - let toast = Toast() - toast.show( - message: error, - view: owner.view, - position: .bottom, - inset: 100 - ) + viewModel.tardyList.bindOnMain(with: self) { owner, tardyList in + switch owner.viewModel.showTardyScreen() { + case .tardyEmptyView: + owner.rootView.do { + $0.finishMeetingButton.isEnabled = false + $0.tardyEmptyView.isHidden = false + $0.titleLabel.isHidden = false + $0.tardyPenaltyView.isHidden = false + $0.noTardyView.isHidden = true + $0.tardyCollectionView.isHidden = true + } + case .tardyListView: + owner.rootView.do { + $0.finishMeetingButton.isEnabled = true + $0.titleLabel.isHidden = false + $0.tardyPenaltyView.isHidden = false + $0.tardyCollectionView.isHidden = false + $0.tardyEmptyView.isHidden = true + $0.noTardyView.isHidden = true + + $0.tardyCollectionView.reloadData() + } + case .noTardyView: + owner.rootView.do { + $0.finishMeetingButton.isEnabled = true + $0.noTardyView.isHidden = false + $0.tardyEmptyView.isHidden = true + $0.titleLabel.isHidden = true + $0.tardyPenaltyView.isHidden = true + $0.tardyCollectionView.isHidden = true + } + } } } } @@ -112,7 +138,7 @@ extension TardyViewController: UICollectionViewDataSource { _ collectionView: UICollectionView, numberOfItemsInSection section: Int ) -> Int { - return viewModel.comers.value.count + return viewModel.tardyList.value.count } func collectionView( @@ -122,16 +148,17 @@ extension TardyViewController: UICollectionViewDataSource { guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: TardyCollectionViewCell.reuseIdentifier, for: indexPath - ) as? TardyCollectionViewCell else { return UICollectionViewCell() } + ) as? TardyCollectionViewCell else { + return UICollectionViewCell() + } - cell.nameLabel.setText( - viewModel.comers.value[indexPath.row].name ?? "", - style: .body06, - color: .gray6 - ) - + guard let tardyName = viewModel.tardyList.value[indexPath.row].name else { + return cell + } + + cell.nameLabel.setText(tardyName, style: .body06, color: .gray6) cell.profileImageView.kf.setImage( - with: URL(string: viewModel.comers.value[indexPath.row].profileImageURL ?? ""), + with: URL(string: viewModel.tardyList.value[indexPath.row].profileImageURL ?? ""), placeholder: UIImage.imgProfile ) diff --git a/KkuMulKum/Source/Promise/ViewModel/PromiseViewModel.swift b/KkuMulKum/Source/Promise/ViewModel/PromiseViewModel.swift index 01e824e8..06243eac 100644 --- a/KkuMulKum/Source/Promise/ViewModel/PromiseViewModel.swift +++ b/KkuMulKum/Source/Promise/ViewModel/PromiseViewModel.swift @@ -7,9 +7,10 @@ import Foundation -enum TappedButton { - case ready - case move +enum TardyScreen { + case tardyEmptyView + case tardyListView + case noTardyView } class PromiseViewModel { @@ -17,62 +18,23 @@ class PromiseViewModel { // MARK: Property - /// 서버 통신을 위해 생성자로 주입받을 약속 ID let promiseID: Int - - /// 현재 페이지 인덱스 let currentPageIndex = ObservablePattern(0) - - /// 약속 정보 let promiseInfo = ObservablePattern(nil) - - /// 우리들의 준비 현황 스택 뷰에 들어갈 정보들 - let participantsInfo = ObservablePattern<[Participant]?>(nil) - - /// 나의 준비현황이 담긴 정보 - /// 설령 데이터가 없다하더라도 약속 시간은 담겨있음. - let myReadyStatus = ObservablePattern(nil) - - /// 현재 준비 상태에 대한 버튼 처리 - let myReadyProgressStatus = ObservablePattern(.none) - - /// 준비 시작 시간 - var readyStartTime = ObservablePattern("") - - /// 준비 소요 시간 - var readyDuration = ObservablePattern("") - - /// 이동 시작 시간 - var moveStartTime = ObservablePattern("") - - /// 이동 소요 시간 - var moveDuration = ObservablePattern("") - - /// 꾸물거림 여부 - var isLate = ObservablePattern(false) - - /// 지각자 유무 여부 - var hasTardy: ObservablePattern = ObservablePattern(false) - - /// 약속 시간 지났는지 여부 - var isPastDue: ObservablePattern = ObservablePattern(false) - - /// 벌칙 정보 - var penalty: ObservablePattern = ObservablePattern("") - - /// 지각자 목록 - var comers: ObservablePattern<[Comer]> = ObservablePattern<[Comer]>([]) - - /// 서버로부터 받아올 에러 메시지 - var errorMessage: ObservablePattern = ObservablePattern("") - - /// 현재 시간 받아오기 위한 dateFormatter 구헌 - private let dateFormatter = DateFormatter().then { - $0.locale = Locale(identifier: "ko_KR") - $0.timeZone = TimeZone(identifier: "Asia/Seoul") - $0.amSymbol = "AM" - $0.pmSymbol = "PM" - } + let myReadyInfo = ObservablePattern(nil) + let myReadyStatus = ObservablePattern(.none) + let isPastDue = ObservablePattern(nil) + let penalty = ObservablePattern(nil) + let dDay = ObservablePattern(nil) + let requestReadyTime = ObservablePattern<[String]>(["", "", "", ""]) + let participantList = ObservablePattern<[Participant]>([]) + let tardyList = ObservablePattern<[Comer]>([]) + let isFinishSuccess = ObservablePattern(nil) + let isDeleteSuccess = ObservablePattern(nil) + let isExitSuccess = ObservablePattern(nil) + let errorMessage = ObservablePattern(nil) + + var pageControlDirection = false private let service: PromiseServiceProtocol @@ -88,78 +50,116 @@ class PromiseViewModel { // MARK: - Extension -extension PromiseViewModel { - /// segmentedControl 인덱스 바뀌었을 때 호출되는 함수 - func segmentIndexDidChange(index: Int) { - currentPageIndex.value = index +private extension PromiseViewModel { + func calculateDday() { + let calendar = Calendar.current + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + dateFormatter.locale = Locale(identifier: "ko_KR") + dateFormatter.timeZone = TimeZone(identifier: "Asia/Seoul") + + guard let dateWithTime = dateFormatter.date(from: promiseInfo.value?.time ?? "") else { return } + + let dateOnly = calendar.startOfDay(for: dateWithTime) + let today = calendar.startOfDay(for: Date()) + let components = calendar.dateComponents([.day], from: today, to: dateOnly) + + dDay.value = components.day } - /// 우리들의 준비 현황 변동되었을 때 호출되는 함수 - func participantInfosDidChanged(infos: [Participant]) { - participantsInfo.value = infos + func calculateReadyInfo() { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + + let timeFormatter = DateFormatter() + timeFormatter.dateFormat = "HH시 mm분" + + guard let promiseTime = myReadyInfo.value?.promiseTime, + let promiseDate = dateFormatter.date(from: promiseTime), + let preparationTime = myReadyInfo.value?.preparationTime, + let travelTime = myReadyInfo.value?.travelTime else { + return + } + + let readyStartTime = promiseDate.addingTimeInterval(-TimeInterval(preparationTime + travelTime + 10) * 60) + let moveStartTime = promiseDate.addingTimeInterval(-TimeInterval(travelTime + 10) * 60) + let preparationHours = preparationTime / 60 + let preparationMinutes = preparationTime % 60 + let travelHours = travelTime / 60 + let travelMinutes = travelTime % 60 + + requestReadyTime.value[0] = timeFormatter.string(from: readyStartTime) + requestReadyTime.value[1] = timeFormatter.string(from: moveStartTime) + requestReadyTime.value[2] = preparationHours == 0 ? "\(preparationMinutes)분" : "\(preparationHours)시간 \(preparationMinutes)분" + requestReadyTime.value[3] = travelHours == 0 ? "\(travelMinutes)분" : "\(travelHours)시간 \(travelMinutes)분" } - /// 준비 현황 버튼 클릭했을 때 현재 시간 반환하는 함수 - func updateReadyStatusTime() -> String { - dateFormatter.dateFormat = "a h:mm" + func getMyReadyStatus() { + guard let info = myReadyInfo.value else { return } + switch (info.preparationStartAt, info.departureAt, info.arrivalAt) { + case (nil, nil, nil): + myReadyStatus.value = .none + case (.some, nil, nil): + myReadyStatus.value = .ready + case (.some, .some, nil): + myReadyStatus.value = .move + case (.some, .some, .some): + myReadyStatus.value = .done + default: + break + } + } +} + +extension PromiseViewModel { + func isEditButtonHidden() -> Bool { + guard let isParticipant = promiseInfo.value?.isParticipant, + let isPastDue = isPastDue.value + else { + return false + } - return dateFormatter.string(from: Date()) + return !(isParticipant && !isPastDue) } - func updateMyReadyProgressStatus() { - myReadyProgressStatus.value = myReadyStatus.value?.preparationStartAt == nil ? .none - : myReadyStatus.value?.departureAt == nil ? .ready - : myReadyStatus.value?.arrivalAt == nil ? .move - : .done + func isReadyInfoEntered() -> Bool { + guard let isParticipant = promiseInfo.value?.isParticipant, + let _ = myReadyInfo.value?.preparationTime, + let _ = myReadyInfo.value?.travelTime else { + return false + } + + return isParticipant } - /// 준비 or 이동 소요 시간 계산하는 함수 - func calculateDuration() { - let preparationHours = (self.myReadyStatus.value?.preparationTime ?? 0) / 60 - let preparationMinutes = (self.myReadyStatus.value?.preparationTime ?? 0) % 60 - - readyDuration.value = preparationHours == 0 ? "\(preparationMinutes)분" : "\(preparationHours)시간 \(preparationMinutes)분" + func showTardyScreen() -> TardyScreen { + guard let isPastDue = isPastDue.value else { return .tardyEmptyView } + let hasTardy = !tardyList.value.isEmpty - let travelHours = (self.myReadyStatus.value?.travelTime ?? 0) / 60 - let travelMinutes = (self.myReadyStatus.value?.travelTime ?? 0) % 60 + if !isPastDue { + return .tardyEmptyView + } + else if isPastDue && hasTardy { + return .tardyListView + } + else if isPastDue && !hasTardy { + return .noTardyView + } - moveDuration.value = travelHours == 0 ? "\(travelMinutes)분" : "\(travelHours)시간 \(travelMinutes)분" + return .tardyEmptyView } - /// 준비 or 이동 시작 시간 계산하는 함수 - func calculateStartTime() { + func convertTime() -> String { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - 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) + guard let promiseTime = promiseInfo.value?.time else { return "" } + guard let promiseDate = dateFormatter.date(from: promiseTime) else { return promiseTime } 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 + 10) * 60) - let moveStartTime = promiseDate.addingTimeInterval(-TimeInterval(moveTime + 10) * 60) + timeFormatter.dateFormat = "M월 d일 a H:mm" - self.readyStartTime.value = timeFormatter.string(from: readyStartTime) - print("준비 시작 시간: \(self.readyStartTime.value)") - - self.moveStartTime.value = timeFormatter.string(from: moveStartTime) - print("이동 시작 시간: \(self.moveStartTime.value)") + return timeFormatter.string(from: promiseDate) } /// 약속 상세 정보 조회 API 구현 함수 @@ -171,10 +171,13 @@ extension PromiseViewModel { guard let success = result?.success, success == true else { + print(">>>>> \(String(describing: result)) : \(#function)") return } promiseInfo.value = result?.data + + calculateDday() } catch { print(">>>>> \(error.localizedDescription) : \(#function)") } @@ -185,15 +188,21 @@ extension PromiseViewModel { func fetchPromiseParticipantList() { Task { do { - let responseBody = try await service.fetchPromiseParticipantList(with: promiseID) + let result = try await service.fetchPromiseParticipantList(with: promiseID) - guard let success = responseBody?.success, - success == true + guard let success = result?.success, + success == true else { + print(">>>>> \(String(describing: result)) : \(#function)") + return + } + + guard let data = result?.data else { + print(">>>>> \("데이터 없음") : \(#function)") return } - participantsInfo.value = responseBody?.data?.participants + participantList.value = data.participants } catch { print(">>>>> \(error.localizedDescription) : \(#function)") } @@ -204,15 +213,19 @@ extension PromiseViewModel { func fetchMyReadyStatus() { Task { do { - let responseBody = try await service.fetchMyReadyStatus(with: promiseID) + let result = try await service.fetchMyReadyStatus(with: promiseID) - guard let success = responseBody?.success, + guard let success = result?.success, success == true else { + print(">>>>> \(String(describing: result)) : \(#function)") return } - myReadyStatus.value = responseBody?.data + myReadyInfo.value = result?.data + + calculateReadyInfo() + getMyReadyStatus() } catch { print(">>>>> \(error.localizedDescription) : \(#function)") } @@ -220,64 +233,67 @@ extension PromiseViewModel { } /// 준비 시작 업데이트 API 구현 함수 - func updatePreparationStatus(completion: @escaping () -> Void) { + func updatePreparationStatus() { Task { do { - let responseBody = try await service.updatePreparationStatus( - with: promiseID - ) + let result = try await service.updatePreparationStatus(with: promiseID) - guard let success = responseBody?.success, + guard let success = result?.success, success == true else { + print(">>>>> \(String(describing: result)) : \(#function)") return } - myReadyProgressStatus.value = .ready - - completion() + fetchMyReadyStatus() + fetchPromiseParticipantList() + } + catch { + print(">>>>> \(error.localizedDescription) : \(#function)") } } } /// 이동 시작 업데이트 API 구현 함수 - func updateDepartureStatus(completion: @escaping () -> Void) { + func updateDepartureStatus() { Task { do { - let responseBody = try await service.updateDepartureStatus( - with: promiseID - ) + let result = try await service.updateDepartureStatus(with: promiseID) - guard let success = responseBody?.success, + guard let success = result?.success, success == true else { + print(">>>>> \(String(describing: result)) : \(#function)") return } - myReadyProgressStatus.value = .move - - completion() + fetchMyReadyStatus() + fetchPromiseParticipantList() + } + catch { + print(">>>>> \(error.localizedDescription) : \(#function)") } } } /// 도착 완료 업데이트 API 구현 함수 - func updateArrivalStatus(completion: @escaping () -> Void) { + func updateArrivalStatus() { Task { do { - let responseBody = try await service.updateArrivalStatus( - with: promiseID - ) + let result = try await service.updateArrivalStatus(with: promiseID) - guard let success = responseBody?.success, + guard let success = result?.success, success == true else { + print(">>>>> \(String(describing: result)) : \(#function)") return } - myReadyProgressStatus.value = .done - - completion() + fetchMyReadyStatus() + fetchPromiseParticipantList() + } + catch { + print(">>>>> \(error.localizedDescription) : \(#function)") } } } @@ -286,24 +302,23 @@ extension PromiseViewModel { func fetchTardyInfo() { Task { do { - let responseBody = try await - service.fetchTardyInfo(with: promiseID) + let result = try await service.fetchTardyInfo(with: promiseID) - guard let success = responseBody?.success, + guard let success = result?.success, success == true else { + print(">>>>> \(String(describing: result)) : \(#function)") return } - guard let data = responseBody?.data else { + guard let data = result?.data else { + print(">>>>> \("데이터 없음") : \(#function)") return } - hasTardy.value = !(data.lateComers.isEmpty) - isPastDue.value = data.isPastDue penalty.value = data.penalty - comers.value = data.lateComers - + isPastDue.value = data.isPastDue + tardyList.value = data.lateComers } catch { print(">>>>> \(error.localizedDescription) : \(#function)") } @@ -311,43 +326,51 @@ extension PromiseViewModel { } /// 약속 완료 API 구현 함수 - func updatePromiseCompletion(completion: @escaping () -> Void) { + func updatePromiseCompletion() { Task { do { - let responseBody = try await service.updatePromiseCompletion(with: promiseID) + let result = try await service.updatePromiseCompletion(with: promiseID) - guard let success = responseBody?.success, + guard let success = result?.success, success == true else { - handleError(errorResponse: responseBody?.error) + guard let message = result?.error?.message else { return } + errorMessage.value = message + + print(">>>>> \(String(describing: result)) : \(#function)") return } - - completion() + + isFinishSuccess.value = success } catch { print(">>>>> \(error.localizedDescription) : \(#function)") } } } - func deletePromise(completion: @escaping () -> Void) { + /// 약속 삭제 API 구현 함수 + func deletePromise() { Task { do { let result = try await service.deletePromise(promiseID: promiseID) guard let success = result?.success, - success == true + success == true else { + print(">>>>> \(String(describing: result)) : \(#function)") return } - completion() + isDeleteSuccess.value = success + } catch { + print(">>>>> \(error.localizedDescription) : \(#function)") } } } - func exitPromise(completion: @escaping () -> Void) { + /// 약속 나가기 API 구현 함수 + func exitPromise() { Task { do { let result = try await service.exitPromise(promiseID: promiseID) @@ -355,33 +378,14 @@ extension PromiseViewModel { guard let success = result?.success, success == true else { + print(">>>>> \(String(describing: result)) : \(#function)") return } - completion() + isExitSuccess.value = success + } catch { + print(">>>>> \(error.localizedDescription) : \(#function)") } } } } - -private extension PromiseViewModel { - func handleError(errorResponse: ErrorResponse?) { - guard let error = errorResponse else { - errorMessage.value = "알 수 없는 에러" - return - } - - switch error.code { - case 40051: - errorMessage.value = "도착하지 않은 참여자가 있습니다." - case 40050: - errorMessage.value = "약속 시간이 지나지 않았습니다." - case 40340: - errorMessage.value = "참여하지 않은 약속입니다." - case 40450: - errorMessage.value = "약속을 찾을 수 없습니다." - default: - errorMessage.value = "알 수 없는 에러" - } - } -}