diff --git a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj index 12388f13..2f5ace6c 100644 --- a/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj +++ b/Runnect-iOS/Runnect-iOS.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ A3D1A77929CF03D200DD54EC /* AuthRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3D1A77829CF03D200DD54EC /* AuthRouter.swift */; }; A3D1A77E29CF09B600DD54EC /* SignInResponseDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3D1A77D29CF09B600DD54EC /* SignInResponseDto.swift */; }; A3D1A78029CF142E00DD54EC /* UserManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3D1A77F29CF142E00DD54EC /* UserManager.swift */; }; + A3DB8C472A0B64830081AF2D /* ActivityRecordDetailVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3DB8C462A0B64830081AF2D /* ActivityRecordDetailVC.swift */; }; A3E55BA029C815B10000D85D /* SignInSocialLoginVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3E55B9F29C815B10000D85D /* SignInSocialLoginVC.swift */; }; A3F67AE2296D33AC001598A2 /* MyPageDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3F67AE1296D33AC001598A2 /* MyPageDto.swift */; }; A3F67AEA296E4936001598A2 /* ActivityRecordInfoDto.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3F67AE9296E4936001598A2 /* ActivityRecordInfoDto.swift */; }; @@ -177,6 +178,7 @@ A3D1A77829CF03D200DD54EC /* AuthRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthRouter.swift; sourceTree = ""; }; A3D1A77D29CF09B600DD54EC /* SignInResponseDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInResponseDto.swift; sourceTree = ""; }; A3D1A77F29CF142E00DD54EC /* UserManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserManager.swift; sourceTree = ""; }; + A3DB8C462A0B64830081AF2D /* ActivityRecordDetailVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityRecordDetailVC.swift; sourceTree = ""; }; A3E55B9F29C815B10000D85D /* SignInSocialLoginVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSocialLoginVC.swift; sourceTree = ""; }; A3E55BA529C8AB0A0000D85D /* Runnect-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Runnect-iOS.entitlements"; sourceTree = ""; }; A3F67AE1296D33AC001598A2 /* MyPageDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageDto.swift; sourceTree = ""; }; @@ -360,6 +362,7 @@ A3BC2F2A2962C3D500198261 /* GoalRewardInfoVC.swift */, A3BC2F2C2962C3F200198261 /* ActivityRecordInfoVC.swift */, A3BC2F2E2962C40A00198261 /* UploadedCourseInfoVC.swift */, + A3DB8C462A0B64830081AF2D /* ActivityRecordDetailVC.swift */, ); path = InfoVC; sourceTree = ""; @@ -1360,6 +1363,7 @@ CEE59FDA29DD6F7D00C791F1 /* Providers.swift in Sources */, CE5875A029601500005D967E /* Toast.swift in Sources */, CE40BB1E2968054F0030ABCA /* BaseResponse.swift in Sources */, + A3DB8C472A0B64830081AF2D /* ActivityRecordDetailVC.swift in Sources */, CE14677C2965C1B100DCEA1B /* RunningRecordVC.swift in Sources */, CE6B63D829673450003F900F /* ListEmptyView.swift in Sources */, A3C2CAD329E4F77C00EC525B /* TermsOfServiceVC.swift in Sources */, @@ -1609,7 +1613,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.runnect.Runnect-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1646,7 +1650,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.runnect.Runnect-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Runnect-iOS/Runnect-iOS/Global/Literal/ImageLiterals.swift b/Runnect-iOS/Runnect-iOS/Global/Literal/ImageLiterals.swift index 58eb8944..29c229b6 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Literal/ImageLiterals.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Literal/ImageLiterals.swift @@ -53,6 +53,8 @@ enum ImageLiterals { static var imgLogo: UIImage { .load(named: "img_logo") } static var imgPaper: UIImage { .load(named: "img_paper") } static var imgPerson: UIImage { .load(named: "img_person") } + static var imgRecordContainerSelected: UIImage { .load(named: "img_record_container_selected") } + static var imgRecordContainer: UIImage { .load(named: "img_record_container") } static var imgStampC1: UIImage { .load(named: "img_stamp_c1") } static var imgStampC2: UIImage { .load(named: "img_stamp_c2") } static var imgStampC3: UIImage { .load(named: "img_stamp_c3") } @@ -76,7 +78,6 @@ enum ImageLiterals { static var imgKakaoLogin: UIImage { .load(named: "img_kakao_login")} } - extension UIImage { static func load(named imageName: String) -> UIImage { guard let image = UIImage(named: imageName, in: nil, compatibleWith: nil) else { diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container.imageset/Contents.json b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container.imageset/Contents.json new file mode 100644 index 00000000..05e9b4fb --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Subtract.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Subtract@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Subtract@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container.imageset/Subtract.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container.imageset/Subtract.png new file mode 100644 index 00000000..5487ae35 Binary files /dev/null and b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container.imageset/Subtract.png differ diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container.imageset/Subtract@2x.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container.imageset/Subtract@2x.png new file mode 100644 index 00000000..0d1a7ae2 Binary files /dev/null and b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container.imageset/Subtract@2x.png differ diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container.imageset/Subtract@3x.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container.imageset/Subtract@3x.png new file mode 100644 index 00000000..972c9b80 Binary files /dev/null and b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container.imageset/Subtract@3x.png differ diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container_selected.imageset/Contents.json b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container_selected.imageset/Contents.json new file mode 100644 index 00000000..05e9b4fb --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container_selected.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Subtract.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Subtract@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Subtract@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container_selected.imageset/Subtract.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container_selected.imageset/Subtract.png new file mode 100644 index 00000000..9fdb2231 Binary files /dev/null and b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container_selected.imageset/Subtract.png differ diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container_selected.imageset/Subtract@2x.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container_selected.imageset/Subtract@2x.png new file mode 100644 index 00000000..e9d311f4 Binary files /dev/null and b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container_selected.imageset/Subtract@2x.png differ diff --git a/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container_selected.imageset/Subtract@3x.png b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container_selected.imageset/Subtract@3x.png new file mode 100644 index 00000000..f1b9eb0f Binary files /dev/null and b/Runnect-iOS/Runnect-iOS/Global/Resource/Assets.xcassets/img_record_container_selected.imageset/Subtract@3x.png differ diff --git a/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift b/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift index 84f9bf13..c869d1e5 100644 --- a/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift +++ b/Runnect-iOS/Runnect-iOS/Global/UIComponents/CustomNavigationBar.swift @@ -164,7 +164,12 @@ extension CustomNavigationBar { extension CustomNavigationBar { @objc private func popToPreviousVC() { + guard let vc = vc else { return } self.vc?.navigationController?.popViewController(animated: true) + if vc.presentingViewController != nil { + self.vc?.dismiss(animated: true) + + } } @objc private func searchLocation() { diff --git a/Runnect-iOS/Runnect-iOS/Network/Model/MyPageModel/GoalRewardInfoModel.swift b/Runnect-iOS/Runnect-iOS/Network/Model/MyPageModel/GoalRewardInfoModel.swift index 68602b35..6405c6bb 100644 --- a/Runnect-iOS/Runnect-iOS/Network/Model/MyPageModel/GoalRewardInfoModel.swift +++ b/Runnect-iOS/Runnect-iOS/Network/Model/MyPageModel/GoalRewardInfoModel.swift @@ -16,17 +16,17 @@ extension GoalRewardInfoModel { static var stampNameList: [GoalRewardInfoModel] { return [ GoalRewardInfoModel(stampImg: ImageLiterals.imgStampC1, stampStandard: "그린 코스 1개"), - GoalRewardInfoModel(stampImg: ImageLiterals.imgStampC2, stampStandard: "그린 코스 5개"), - GoalRewardInfoModel(stampImg: ImageLiterals.imgStampC3, stampStandard: "그린 코스 10개"), + GoalRewardInfoModel(stampImg: ImageLiterals.imgStampC2, stampStandard: "그린 코스 10개"), + GoalRewardInfoModel(stampImg: ImageLiterals.imgStampC3, stampStandard: "그린 코스 30개"), GoalRewardInfoModel(stampImg: ImageLiterals.imgStampS1, stampStandard: "스크랩 1회"), - GoalRewardInfoModel(stampImg: ImageLiterals.imgStampS2, stampStandard: "스크랩 5회"), - GoalRewardInfoModel(stampImg: ImageLiterals.imgStampS3, stampStandard: "스크랩 10회"), + GoalRewardInfoModel(stampImg: ImageLiterals.imgStampS2, stampStandard: "스크랩 20회"), + GoalRewardInfoModel(stampImg: ImageLiterals.imgStampS3, stampStandard: "스크랩 40회"), GoalRewardInfoModel(stampImg: ImageLiterals.imgStampU1, stampStandard: "업로드 1회"), - GoalRewardInfoModel(stampImg: ImageLiterals.imgStampU2, stampStandard: "업로드 5회"), - GoalRewardInfoModel(stampImg: ImageLiterals.imgStampU3, stampStandard: "업로드 10회"), + GoalRewardInfoModel(stampImg: ImageLiterals.imgStampU2, stampStandard: "업로드 10회"), + GoalRewardInfoModel(stampImg: ImageLiterals.imgStampU3, stampStandard: "업로드 30회"), GoalRewardInfoModel(stampImg: ImageLiterals.imgStampR1, stampStandard: "달리기 1회"), - GoalRewardInfoModel(stampImg: ImageLiterals.imgStampR2, stampStandard: "달리기 5회"), - GoalRewardInfoModel(stampImg: ImageLiterals.imgStampR2, stampStandard: "달리기 10회") + GoalRewardInfoModel(stampImg: ImageLiterals.imgStampR2, stampStandard: "달리기 15회"), + GoalRewardInfoModel(stampImg: ImageLiterals.imgStampR2, stampStandard: "달리기 30회") ] } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordDetailVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordDetailVC.swift new file mode 100644 index 00000000..2412cf8b --- /dev/null +++ b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordDetailVC.swift @@ -0,0 +1,276 @@ +// +// ActivityRecordDetailVC.swift +// Runnect-iOS +// +// Created by 몽이 누나 on 2023/05/10. +// + +import UIKit + +import SnapKit +import Then + +final class ActivityRecordDetailVC: UIViewController { + + // MARK: - Properties + + private let recordProvider = Providers.recordProvider + + private var activityRecordList = [ActivityRecord]() + + private var courseId: Int? + + private var publicCourseId: Int? + + // MARK: - UI Components + + private lazy var navibar = CustomNavigationBar(self, type: .titleWithLeftButton) + + private let moreButton = UIButton(type: .system).then { + $0.setImage(ImageLiterals.icMore, for: .normal) + $0.tintColor = .g1 + } + + private lazy var middleScorollView = UIScrollView().then { + $0.isScrollEnabled = true + $0.showsVerticalScrollIndicator = false + } + + private let mapImageView = UIImageView() + + private let courseTitleLabel = UILabel().then { + $0.text = "제목" + $0.textColor = .g1 + $0.font = .h4 + } + + private let recordDateInfoView = CourseDetailInfoView(title: "날짜", description: "0000.00.00") + + private let recordDepartureInfoView = CourseDetailInfoView(title: "출발지", description: "서울시 영등포구") + + private lazy var recordInfoStackView = UIStackView(arrangedSubviews: [recordDateInfoView, recordDepartureInfoView]).then { + $0.axis = .vertical + $0.spacing = 6 + $0.distribution = .fillEqually + } + + private let firstHorizontalDivideLine = UIView() + + private let secondHorizontalDivideLine = UIView() + + private lazy var recordDistanceLabel = setGreyTitle().then { + $0.text = "거리" + } + + private lazy var recordRunningTimeLabel = setGreyTitle().then { + $0.text = "이동 시간" + } + + private lazy var recordAveragePaceLabel = setGreyTitle().then { + $0.text = "평균 페이스" + } + + private lazy var recordDistanceValueLabel = setBlackTitle().then { + $0.text = "5.1km" + } + + private lazy var recordRunningTimeValueLabel = setBlackTitle().then { + $0.text = "00:28:07" + } + + private lazy var recordAveragePaceValueLabel = setBlackTitle().then { + $0.text = "5’31’’" + } + + private lazy var recordDistanceStackView = setDetailInfoStakcView(title: recordDistanceLabel, value: recordDistanceValueLabel) + + private lazy var recordRunningTimeStackView = setDetailInfoStakcView(title: recordRunningTimeLabel, value: recordRunningTimeValueLabel) + + private lazy var recordAveragePaceStackView = setDetailInfoStakcView(title: recordAveragePaceLabel, value: recordAveragePaceValueLabel) + + private let firstVerticalDivideLine = UIView() + private let secondVerticalDivideLine = UIView() + + private lazy var recordSubInfoStackView = UIStackView(arrangedSubviews: [recordDistanceStackView, firstVerticalDivideLine, recordRunningTimeStackView, secondVerticalDivideLine, recordAveragePaceStackView]).then { + $0.axis = .horizontal + $0.spacing = 3 + $0.distribution = .fill + } + + // MARK: - View Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + hideTabBar(wantsToHide: true) + setNavigationBar() + setUI() + setLayout() + } +} + +// MARK: - Methods + +extension ActivityRecordDetailVC { + func setCourseId(courseId: Int?, publicCourseId: Int?) { + self.courseId = courseId + self.publicCourseId = publicCourseId + } + + func setDetailInfoStakcView(title: UIView, value: UIView) -> UIStackView { + let stackView = UIStackView(arrangedSubviews: [title, value]) + stackView.axis = .vertical + stackView.alignment = .center + stackView.spacing = 2 + return stackView + } + + func setBlackTitle() -> UILabel { + let label = UILabel() + label.textColor = .g1 + label.font = .h3 + return label + } + + func setGreyTitle() -> UILabel { + let label = UILabel() + label.textColor = .g2 + label.font = .b4 + return label + } +} + +// MARK: - Layout Helpers + +extension ActivityRecordDetailVC { + private func setUI() { + view.backgroundColor = .w1 + middleScorollView.backgroundColor = .w1 + mapImageView.backgroundColor = .g3 + firstHorizontalDivideLine.backgroundColor = .g5 + secondHorizontalDivideLine.backgroundColor = .g5 + firstVerticalDivideLine.backgroundColor = .g2 + secondVerticalDivideLine.backgroundColor = .g2 + } + + private func setNavigationBar() { + view.addSubview(navibar) + view.addSubview(moreButton) + + navibar.snp.makeConstraints { make in + make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.height.equalTo(48) + } + + moreButton.snp.makeConstraints { make in + make.trailing.equalTo(self.view.safeAreaLayoutGuide).inset(16) + make.centerY.equalTo(navibar) + } + } + + private func setLayout() { + view.addSubviews(middleScorollView) + + middleScorollView.snp.makeConstraints { make in + make.top.equalTo(navibar.snp.bottom) + make.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.bottom.equalToSuperview() + } + + middleScorollView.addSubviews(mapImageView, courseTitleLabel, firstHorizontalDivideLine, recordInfoStackView, secondHorizontalDivideLine) + + mapImageView.snp.makeConstraints { make in + make.top.equalToSuperview() + make.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.height.equalTo(middleScorollView.snp.width).multipliedBy(1.13) + } + + courseTitleLabel.snp.makeConstraints { make in + make.top.equalTo(mapImageView.snp.bottom).offset(31) + make.leading.equalTo(view.safeAreaLayoutGuide).offset(16) + } + + firstHorizontalDivideLine.snp.makeConstraints { make in + make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16) + make.height.equalTo(2) + make.top.equalTo(courseTitleLabel.snp.bottom).offset(7) + } + + recordInfoStackView.snp.makeConstraints { make in + make.top.equalTo(firstHorizontalDivideLine.snp.bottom).offset(20) + make.leading.trailing.equalToSuperview().inset(16) + } + + firstVerticalDivideLine.snp.makeConstraints { make in + make.width.equalTo(1) + } + + secondVerticalDivideLine.snp.makeConstraints { make in + make.width.equalTo(1) + } + + secondHorizontalDivideLine.snp.makeConstraints { make in + make.leading.trailing.equalTo(view.safeAreaLayoutGuide) + make.height.equalTo(7) + make.top.equalTo(recordInfoStackView.snp.bottom).offset(36) + } + + setRecordSubInfoStackView() + } + + private func setRecordSubInfoStackView() { + middleScorollView.addSubview(recordSubInfoStackView) + + let screenWidth = UIScreen.main.bounds.width + let containerViewWidth = screenWidth - 32 + let stackViewWidth = Int(containerViewWidth - 2) / 3 + + recordDistanceStackView.snp.makeConstraints { make in + make.width.equalTo(stackViewWidth) + } + + recordRunningTimeStackView.snp.makeConstraints { make in + make.width.equalTo(stackViewWidth) + } + + recordAveragePaceStackView.snp.makeConstraints { make in + make.width.equalTo(stackViewWidth) + } + + recordSubInfoStackView.snp.makeConstraints { make in + make.top.equalTo(secondHorizontalDivideLine.snp.bottom).offset(23) + make.centerX.equalToSuperview() + } + } +} + +// MARK: - Network + +extension ActivityRecordDetailVC { + func getActivityRecordDetailWithPath() { + LoadingIndicator.showLoading() + recordProvider.request(.getActivityRecordInfo) { [weak self] response in + LoadingIndicator.hideLoading() + guard let self = self else { return } + switch response { + case .success(let result): + let status = result.statusCode + if 200..<300 ~= status { + do { + let responseDto = try result.map(BaseResponse.self) + guard let data = responseDto.data else { return } + //self.setData(activityRecordList: data.records) + } catch { + print(error.localizedDescription) + } + } + if status >= 400 { + print("400 error") + self.showNetworkFailureToast() + } + case .failure(let error): + print(error.localizedDescription) + self.showNetworkFailureToast() + } + } + } +} diff --git a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordInfoTableView/ActivityRecordInfoTVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordInfoTableView/ActivityRecordInfoTVC.swift index 7edc5a86..5adb737f 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordInfoTableView/ActivityRecordInfoTVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordInfoTableView/ActivityRecordInfoTVC.swift @@ -15,18 +15,21 @@ final class ActivityRecordInfoTVC: UITableViewCell { // MARK: - UI Components - private let activityRecordContainerView = UIView().then { - $0.layer.cornerRadius = 10 - $0.layer.borderWidth = 1 - $0.layer.borderColor = UIColor.m5.cgColor + var activityRecordContainerView = UIImageView().then { + $0.image = ImageLiterals.imgRecordContainer + } + + private lazy var horizontalDivideLine = UIView().then { + setLineDot(view: $0) } - private let horizontalDivideLine = UIView() private let firstVerticalDivideLine = UIView() private let secondVerticalDivideLine = UIView() private let activityRecordMapImage = UIImageView().then { $0.layer.cornerRadius = 10 + $0.layer.borderWidth = 1.5 + $0.layer.borderColor = UIColor.g4.cgColor $0.clipsToBounds = true } @@ -83,11 +86,17 @@ final class ActivityRecordInfoTVC: UITableViewCell { super.init(style: style, reuseIdentifier: reuseIdentifier) setUI() setLayout() + setLineDot(view: self.horizontalDivideLine) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + self.activityRecordContainerView.image = selected ? ImageLiterals.imgRecordContainerSelected : ImageLiterals.imgRecordContainer + } } // MARK: - Methods @@ -163,6 +172,17 @@ extension ActivityRecordInfoTVC { label.font = .b8 return label } + + func setLineDot(view: UIView) { + let borderLayer = CAShapeLayer() + borderLayer.strokeColor = UIColor.g4.cgColor + borderLayer.lineDashPattern = [4, 4] + borderLayer.frame = view.bounds + borderLayer.fillColor = nil + borderLayer.path = UIBezierPath(rect: view.bounds).cgPath + + view.layer.addSublayer(borderLayer) + } } extension ActivityRecordInfoTVC { @@ -170,10 +190,10 @@ extension ActivityRecordInfoTVC { // MARK: - Layout Helpers func setUI() { - activityRecordMapImage.backgroundColor = .g3 horizontalDivideLine.backgroundColor = .g4 firstVerticalDivideLine.backgroundColor = .g4 secondVerticalDivideLine.backgroundColor = .g4 + self.backgroundColor = .clear } func setLayout() { @@ -194,7 +214,7 @@ extension ActivityRecordInfoTVC { activityRecordMapImage.snp.makeConstraints { make in make.top.equalToSuperview().offset(13) - make.leading.equalToSuperview().offset(15) + make.leading.equalToSuperview().offset(20) make.width.equalTo(86) make.height.equalTo(85) } @@ -206,7 +226,7 @@ extension ActivityRecordInfoTVC { horizontalDivideLine.snp.makeConstraints { make in make.top.equalTo(activityRecordMapImage.snp.bottom).offset(8) - make.leading.trailing.equalToSuperview().inset(10) + make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(1) } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordInfoVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordInfoVC.swift index eab49a64..0501a3f2 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordInfoVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordInfoVC.swift @@ -19,25 +19,55 @@ final class ActivityRecordInfoVC: UIViewController { private var activityRecordList = [ActivityRecord]() + private var isEditMode: Bool = false + // MARK: - UI Components - private lazy var navibar = CustomNavigationBar(self, type: .titleWithLeftButton).setTitle("활동 기록") + private lazy var navibar = CustomNavigationBar(self, type: .titleWithLeftButton).setTitle("러닝 기록") private lazy var activityRecordTableView = UITableView().then { $0.showsVerticalScrollIndicator = false $0.separatorStyle = .none + $0.contentInset = UIEdgeInsets(top: 19, left: 0, bottom: 0, right: 0) + $0.allowsMultipleSelection = true } - - // MARK: - View Life Cycle + private let emptyView = ListEmptyView(description: "아직 러닝 기록이 없어요!\n코스를 그리고 달려보세요", buttonTitle: "코스 그리기") + + private let editRecordContainerView = UIView() + + private lazy var totalNumOfRecordlabel = UILabel().then { + $0.font = .b6 + $0.textColor = .g2 + $0.text = "총 기록 0개" + } + + private let editButton = UIButton(type: .custom).then { + $0.setTitle("편집", for: .normal) + $0.setTitleColor(.m1, for: .normal) + $0.titleLabel?.font = .b7 + $0.layer.borderColor = UIColor.m1.cgColor + $0.layer.borderWidth = 1 + $0.layer.cornerRadius = 11 + } + + private lazy var deleteRecordButton = CustomButton(title: "삭제하기").then { + $0.isHidden = true + $0.isEnabled = false + } + + // MARK: - View Life Cycle + override func viewDidLoad() { super.viewDidLoad() setNavigationBar() setUI() setLayout() + setAddTarget() setDelegate() register() getActivityRecordInfo() + self.hideTabBar(wantsToHide: true) } } @@ -47,22 +77,55 @@ extension ActivityRecordInfoVC { private func setData(activityRecordList: [ActivityRecord]) { self.activityRecordList = activityRecordList activityRecordTableView.reloadData() + self.emptyView.isHidden = !activityRecordList.isEmpty + totalNumOfRecordlabel.text = "총 기록 \(activityRecordList.count)개" } private func setDelegate() { self.activityRecordTableView.delegate = self self.activityRecordTableView.dataSource = self + self.emptyView.delegate = self } private func register() { self.activityRecordTableView.register(ActivityRecordInfoTVC.self, forCellReuseIdentifier: ActivityRecordInfoTVC.className) } + + private func setAddTarget() { + self.editButton.addTarget(self, action: #selector(editButtonDidTap), for: .touchUpInside) + } } +// MARK: - @objc Function + +extension ActivityRecordInfoVC { + @objc func editButtonDidTap() { + if isEditMode { + self.totalNumOfRecordlabel.text = "총 기록 \(self.activityRecordList.count)개" + self.editButton.setTitle("편집", for: .normal) + self.deleteRecordButton.isHidden = true + if let selectedRows = activityRecordTableView.indexPathsForSelectedRows { + for indexPath in selectedRows { + activityRecordTableView.deselectRow(at: indexPath, animated: true) + } + } + self.deleteRecordButton.isEnabled = false + self.deleteRecordButton.setTitle(title: "삭제하기") + self.activityRecordTableView.reloadData() + isEditMode = false + } else { + self.totalNumOfRecordlabel.text = "기록 선택" + self.editButton.setTitle("취소", for: .normal) + self.deleteRecordButton.isHidden = false + self.activityRecordTableView.reloadData() + isEditMode = true + } + } +} + +// MARK: - Layout Helpers + extension ActivityRecordInfoVC { - - // MARK: - Layout Helpers - private func setNavigationBar() { view.addSubview(navibar) @@ -74,17 +137,50 @@ extension ActivityRecordInfoVC { private func setUI() { view.backgroundColor = .w1 - activityRecordTableView.backgroundColor = .w1 + activityRecordTableView.backgroundColor = .m3 + editRecordContainerView.backgroundColor = .w1 } private func setLayout() { - view.addSubview(activityRecordTableView) + view.addSubviews(editRecordContainerView, activityRecordTableView, deleteRecordButton) + activityRecordTableView.addSubviews(emptyView) + + editRecordContainerView.snp.makeConstraints { make in + make.top.equalTo(navibar.snp.bottom) + make.leading.trailing.equalToSuperview() + make.height.equalTo(38) + } + + editRecordContainerView.addSubviews(totalNumOfRecordlabel, editButton) + + totalNumOfRecordlabel.snp.makeConstraints { make in + make.leading.equalToSuperview().inset(16) + make.top.equalToSuperview().offset(10) + } + + editButton.snp.makeConstraints { make in + make.trailing.equalToSuperview().inset(16) + make.width.equalTo(47) + make.height.equalTo(22) + make.top.equalToSuperview().offset(5) + } activityRecordTableView.snp.makeConstraints { make in - make.top.equalTo(navibar.snp.bottom).offset(16) + make.top.equalTo(editRecordContainerView.snp.bottom) make.leading.trailing.equalTo(view.safeAreaLayoutGuide) make.bottom.equalToSuperview() } + + deleteRecordButton.snp.makeConstraints { make in + make.bottom.equalToSuperview().inset(32) + make.leading.trailing.equalToSuperview().inset(16) + make.height.equalTo(44) + } + + emptyView.snp.makeConstraints { make in + make.center.equalToSuperview() + make.leading.trailing.equalToSuperview().inset(80) + } } } @@ -94,6 +190,39 @@ extension ActivityRecordInfoVC: UITableViewDelegate { func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 193 } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard tableView.cellForRow(at: indexPath) is ActivityRecordInfoTVC else { return } + guard let selectedRecords = tableView.indexPathsForSelectedRows else { return } + let activityRecordList = activityRecordList[indexPath.item] + //ActivityRecordDetailVC.setCourseId(courseId: <#T##Int?#>, publicCourseId: <#T##Int?#>) + + if isEditMode { + self.deleteRecordButton.isEnabled = true + let countSelectedRows = selectedRecords.count + self.deleteRecordButton.setTitle(title: "삭제하기(\(countSelectedRows))") + } else { + tableView.deselectRow(at: indexPath, animated: true) + self.deleteRecordButton.setTitle(title: "삭제하기") + // 편집 모드가 아닐 때 상세 페이지로 이동 + + } + } + + func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { + guard tableView.cellForRow(at: indexPath) is ActivityRecordInfoTVC else { return } + guard let selectedRecords = tableView.indexPathsForSelectedRows else { + self.deleteRecordButton.isEnabled = false + self.deleteRecordButton.setTitle(title: "삭제하기") + return } + if isEditMode { + self.deleteRecordButton.isEnabled = true + let countSelectedRows = selectedRecords.count + self.deleteRecordButton.setTitle(title: "삭제하기(\(countSelectedRows))") + } else { + tableView.deselectRow(at: indexPath, animated: true) + self.deleteRecordButton.setTitle(title: "삭제하기") + } } } // MARK: - UITableViewDataSource @@ -107,10 +236,34 @@ extension ActivityRecordInfoVC: UITableViewDataSource { guard let activityRecordCell = tableView.dequeueReusableCell(withIdentifier: ActivityRecordInfoTVC.className, for: indexPath) as? ActivityRecordInfoTVC else { return UITableViewCell()} activityRecordCell.selectionStyle = .none activityRecordCell.setData(model: activityRecordList[indexPath.item]) + if isEditMode { + // 선택된 셀에 대한 표시 업데이트 + if let selectedRecords = tableView.indexPathsForSelectedRows, selectedRecords.contains(indexPath) { + activityRecordCell.activityRecordContainerView.image = ImageLiterals.imgRecordContainerSelected + } else { + activityRecordCell.activityRecordContainerView.image = ImageLiterals.imgRecordContainer + } + + } else { + activityRecordCell.selectionStyle = .none + // 선택된 셀들을 순회하면서 미선택 이미지로 변경 + for indexPath in tableView.indexPathsForSelectedRows ?? [] { + guard let cell = tableView.cellForRow(at: indexPath) as? ActivityRecordInfoTVC else { continue } + cell.activityRecordContainerView.image = ImageLiterals.imgRecordContainer + } + } return activityRecordCell } } +// MARK: - ListEmptyViewDelegate + +extension ActivityRecordInfoVC: ListEmptyViewDelegate { + func emptyViewButtonTapped() { + self.tabBarController?.selectedIndex = 0 + } +} + // MARK: - Network extension ActivityRecordInfoVC { diff --git a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/MyPageVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/MyPageVC.swift index 070e91f9..a3026c5f 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/MyPageVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/MyPageVC.swift @@ -73,7 +73,7 @@ final class MyPageVC: UIViewController { $0.addGestureRecognizer(tap) } - private lazy var activityRecordInfoView = makeInfoView(title: "활동 기록").then { + private lazy var activityRecordInfoView = makeInfoView(title: "러닝 기록").then { let tap = UITapGestureRecognizer(target: self, action: #selector(self.touchUpActivityRecordInfoView)) $0.addGestureRecognizer(tap) } @@ -97,7 +97,8 @@ final class MyPageVC: UIViewController { private let versionInfoValueLabel = UILabel().then { $0.textColor = .g2 $0.font = .b2 - $0.text = "v. 1.0.1" + let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + $0.text = "v. \(version ?? "1.0.0")" } // MARK: - View Life Cycle @@ -113,6 +114,7 @@ final class MyPageVC: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.getMyPageInfo() + self.hideTabBar(wantsToHide: false) } } @@ -175,8 +177,7 @@ extension MyPageVC { private func pushToNicknameEditorVC() { let nicknameEditorVC = NicknameEditorVC() nicknameEditorVC.delegate = self - nicknameEditorVC.modalPresentationStyle = .overFullScreen - self.present(nicknameEditorVC, animated: false) + self.navigationController?.pushViewController(nicknameEditorVC, animated: true) } private func pushToSettingVC() { diff --git a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/NicknameEditorVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/NicknameEditorVC.swift index d01a8c34..ed576874 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/NicknameEditorVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/NicknameEditorVC.swift @@ -27,29 +27,38 @@ final class NicknameEditorVC: UIViewController { // MARK: - UI Components - private let editorContentView = UIView().then { - $0.layer.cornerRadius = 10 - } - - private let nickNameEditLabel = UILabel().then { - $0.text = "닉네임 수정" - $0.textColor = .g1 - $0.font = .h5 - } - + private lazy var navibar = CustomNavigationBar(self, type: .titleWithLeftButton).setTitle("닉네임 수정") + private let nickNameTextField = UITextField().then { $0.resignFirstResponder() $0.text = nil $0.textColor = .g1 - $0.font = .b6 + $0.font = .h5 + $0.textAlignment = .center $0.attributedPlaceholder = NSAttributedString( string: "닉네임을 입력하세요", - attributes: [NSAttributedString.Key.foregroundColor: UIColor.g2, NSAttributedString.Key.font: UIFont.b6] + attributes: [NSAttributedString.Key.foregroundColor: UIColor.g2, NSAttributedString.Key.font: UIFont.h5] ) $0.keyboardType = .webSearch } - private let horizontalDivideLine = UIView() + private let personImageView = UIImageView().then { + $0.image = ImageLiterals.imgPerson + } + + private let nickNameContainer = UIView().then { + $0.layer.cornerRadius = 5 + $0.layer.borderColor = UIColor.m1.cgColor + $0.layer.borderWidth = 1 + } + + private lazy var finishNickNameLabel = UILabel().then { + $0.text = "완료" + $0.font = .h4 + $0.textColor = .m1 + let tap = UITapGestureRecognizer(target: self, action: #selector(finishNickNameLabelDidTap)) + self.view.addGestureRecognizer(tap) + } // MARK: - View Life Cycle @@ -66,15 +75,7 @@ final class NicknameEditorVC: UIViewController { // MARK: - Method -extension NicknameEditorVC { - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - super.touchesBegan(touches, with: event) - if let touch = touches.first, touch.view == self.view { - dismiss(animated: false) - didNicknameReturn() - } - } - +extension NicknameEditorVC { private func setAddTarget() { nickNameTextField.addTarget(self, action: #selector(textFieldTextDidChange), for: .editingChanged) } @@ -113,6 +114,11 @@ extension NicknameEditorVC { self.nickNameTextField.text = String(newString) } } + + @objc private func finishNickNameLabelDidTap() { + didNicknameReturn() + self.navigationController?.popViewController(animated: false) + } } extension NicknameEditorVC { @@ -120,37 +126,39 @@ extension NicknameEditorVC { // MARK: - Layout Helpers private func setUI() { - self.tabBarController?.tabBar.isHidden = true - view.backgroundColor = .black.withAlphaComponent(0.8) - editorContentView.backgroundColor = .w1 - horizontalDivideLine.backgroundColor = .g3 + view.backgroundColor = .w1 } private func setLayout() { - view.addSubview(editorContentView) + view.addSubviews(navibar, finishNickNameLabel, personImageView, nickNameContainer) - editorContentView.snp.makeConstraints { make in - make.centerY.equalToSuperview() - make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16) - make.height.equalTo(111) + navibar.snp.makeConstraints { make in + make.leading.top.trailing.equalTo(view.safeAreaLayoutGuide) + make.height.equalTo(48) } - editorContentView.addSubviews(nickNameEditLabel, nickNameTextField, horizontalDivideLine) + finishNickNameLabel.snp.makeConstraints { make in + make.trailing.equalToSuperview().inset(23) + make.top.equalTo(view.safeAreaLayoutGuide).offset(12) + } - nickNameEditLabel.snp.makeConstraints { make in - make.top.leading.equalToSuperview().offset(24) + personImageView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.width.height.equalTo(96) + make.top.equalTo(navibar.snp.bottom).offset(98) } - nickNameTextField.snp.makeConstraints { make in - make.top.equalTo(nickNameEditLabel.snp.bottom).offset(20) - make.leading.trailing.equalToSuperview().inset(24) + nickNameContainer.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(30) + make.height.equalTo(44) + make.top.equalTo(personImageView.snp.bottom).offset(51) } - horizontalDivideLine.snp.makeConstraints { make in - make.top.equalTo(nickNameTextField.snp.bottom).offset(10) - make.centerX.equalToSuperview() - make.width.equalTo(nickNameTextField.snp.width) - make.height.equalTo(0.5) + nickNameContainer.addSubview(nickNameTextField) + + nickNameTextField.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.leading.trailing.equalToSuperview() } } }