diff --git a/echog/echog.xcodeproj/project.pbxproj b/echog/echog.xcodeproj/project.pbxproj index f2190bc..989c656 100644 --- a/echog/echog.xcodeproj/project.pbxproj +++ b/echog/echog.xcodeproj/project.pbxproj @@ -32,6 +32,11 @@ 92365BA52D09B0C4001D4A71 /* DiaryDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92365BA42D09B0BF001D4A71 /* DiaryDTO.swift */; }; 92365BC02D1BC201001D4A71 /* DiaryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92365BBF2D1BC1FB001D4A71 /* DiaryCell.swift */; }; 92365BC22D1C16A4001D4A71 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92365BC12D1C16A0001D4A71 /* HeaderView.swift */; }; + 92365BDC2D1D0440001D4A71 /* MyPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92365BDB2D1D043A001D4A71 /* MyPageViewController.swift */; }; + 92365BDE2D1D72BC001D4A71 /* MyPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92365BDD2D1D72B7001D4A71 /* MyPage.swift */; }; + 92365BE12D1D7390001D4A71 /* MyVoteListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92365BE02D1D7387001D4A71 /* MyVoteListViewController.swift */; }; + 92365BFD2D2EBBCF001D4A71 /* UnderlineSegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92365BFC2D2EBBC4001D4A71 /* UnderlineSegmentedControl.swift */; }; + 92365BFF2D2EC56E001D4A71 /* VoteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92365BFE2D2EC565001D4A71 /* VoteCell.swift */; }; 9298888F2CDDC97E0011DE3C /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9298888E2CDDC97E0011DE3C /* SnapKit */; }; 92DD8F092CEC41C2001197D0 /* HTTP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92DD8F082CEC41BB001197D0 /* HTTP.swift */; }; 92DD8F262CEC8243001197D0 /* NetworkPluginProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92DD8F252CEC8238001197D0 /* NetworkPluginProtocol.swift */; }; @@ -111,6 +116,11 @@ 92365BA42D09B0BF001D4A71 /* DiaryDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryDTO.swift; sourceTree = ""; }; 92365BBF2D1BC1FB001D4A71 /* DiaryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryCell.swift; sourceTree = ""; }; 92365BC12D1C16A0001D4A71 /* HeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderView.swift; sourceTree = ""; }; + 92365BDB2D1D043A001D4A71 /* MyPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageViewController.swift; sourceTree = ""; }; + 92365BDD2D1D72B7001D4A71 /* MyPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPage.swift; sourceTree = ""; }; + 92365BE02D1D7387001D4A71 /* MyVoteListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyVoteListViewController.swift; sourceTree = ""; }; + 92365BFC2D2EBBC4001D4A71 /* UnderlineSegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnderlineSegmentedControl.swift; sourceTree = ""; }; + 92365BFE2D2EC565001D4A71 /* VoteCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoteCell.swift; sourceTree = ""; }; 923741FC2CE35EC800A7BE19 /* EchogUITest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EchogUITest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 92DD8F082CEC41BB001197D0 /* HTTP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTP.swift; sourceTree = ""; }; 92DD8F252CEC8238001197D0 /* NetworkPluginProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkPluginProtocol.swift; sourceTree = ""; }; @@ -188,7 +198,6 @@ isa = PBXGroup; children = ( 92DD8F662CECB396001197D0 /* Mock */, - 92DD8F4B2CEC9979001197D0 /* Model */, 92DD8F482CEC9942001197D0 /* User */, 9204707B2C9A75D000D547FB /* Application */, 920470912C9ACBA700D547FB /* Presentation */, @@ -239,6 +248,7 @@ 920470882C9A909A00D547FB /* Compoment */ = { isa = PBXGroup; children = ( + 92365BFC2D2EBBC4001D4A71 /* UnderlineSegmentedControl.swift */, 920470A72C9BC44B00D547FB /* View */, 920470A62C9BC42400D547FB /* Protocol */, 920470892C9A90AD00D547FB /* Button */, @@ -257,6 +267,7 @@ 920470912C9ACBA700D547FB /* Presentation */ = { isa = PBXGroup; children = ( + 92365BDF2D1D737A001D4A71 /* MyPage */, 92365B572D05D96D001D4A71 /* InformationViewController.swift */, 9204706B2C9A6FC200D547FB /* CalendarViewController.swift */, 92365B9A2D0833DA001D4A71 /* HomeViewController.swift */, @@ -274,6 +285,7 @@ 920470932C9ACBBF00D547FB /* Data */ = { isa = PBXGroup; children = ( + 92DD8F4B2CEC9979001197D0 /* Model */, ); path = Data; sourceTree = ""; @@ -310,12 +322,22 @@ 92365BBE2D1BC1F5001D4A71 /* Cell */ = { isa = PBXGroup; children = ( + 92365BFE2D2EC565001D4A71 /* VoteCell.swift */, 92365BC12D1C16A0001D4A71 /* HeaderView.swift */, 92365BBF2D1BC1FB001D4A71 /* DiaryCell.swift */, ); path = Cell; sourceTree = ""; }; + 92365BDF2D1D737A001D4A71 /* MyPage */ = { + isa = PBXGroup; + children = ( + 92365BE02D1D7387001D4A71 /* MyVoteListViewController.swift */, + 92365BDB2D1D043A001D4A71 /* MyPageViewController.swift */, + ); + path = MyPage; + sourceTree = ""; + }; 92DD8F062CEC417D001197D0 /* Infrastructure */ = { isa = PBXGroup; children = ( @@ -405,6 +427,7 @@ 92DD8F4B2CEC9979001197D0 /* Model */ = { isa = PBXGroup; children = ( + 92365BDD2D1D72B7001D4A71 /* MyPage.swift */, 92365BA42D09B0BF001D4A71 /* DiaryDTO.swift */, 92DD8F4C2CEC997F001197D0 /* UserDTO.swift */, ); @@ -581,6 +604,7 @@ 92365BA52D09B0C4001D4A71 /* DiaryDTO.swift in Sources */, 92DD8F4A2CEC995C001197D0 /* UserLoginNetworkBuilder.swift in Sources */, 92DD8F432CEC9587001197D0 /* BaseURLRegistrable.swift in Sources */, + 92365BFD2D2EBBCF001D4A71 /* UnderlineSegmentedControl.swift in Sources */, 920470812C9A762200D547FB /* Font.swift in Sources */, 9204706C2C9A6FC200D547FB /* CalendarViewController.swift in Sources */, 92365B872D073447001D4A71 /* TextFieldView.swift in Sources */, @@ -588,10 +612,14 @@ 9204708B2C9A90CA00D547FB /* MainButton.swift in Sources */, 92DD8F382CEC87D5001197D0 /* NetworkError.swift in Sources */, 920470832C9A768200D547FB /* UIFont+.swift in Sources */, + 92365BDC2D1D0440001D4A71 /* MyPageViewController.swift in Sources */, + 92365BDE2D1D72BC001D4A71 /* MyPage.swift in Sources */, 92DD8F282CEC82FC001197D0 /* NetworkSerializable.swift in Sources */, 920470682C9A6FC200D547FB /* AppDelegate.swift in Sources */, 92DD8F3E2CEC93EE001197D0 /* NetworkManager.swift in Sources */, + 92365BE12D1D7390001D4A71 /* MyVoteListViewController.swift in Sources */, 92DD8F512CEC9BDA001197D0 /* UserNetwork.swift in Sources */, + 92365BFF2D2EC56E001D4A71 /* VoteCell.swift in Sources */, 920470852C9A76F700D547FB /* UIFontMetrics+.swift in Sources */, 92DD8F4F2CEC9B9A001197D0 /* KeyChain.swift in Sources */, 92DD8F472CEC95A8001197D0 /* BaseURLManager.swift in Sources */, diff --git a/echog/echog/Model/DiaryDTO.swift b/echog/echog/Data/Model/DiaryDTO.swift similarity index 100% rename from echog/echog/Model/DiaryDTO.swift rename to echog/echog/Data/Model/DiaryDTO.swift diff --git a/echog/echog/Data/Model/MyPage.swift b/echog/echog/Data/Model/MyPage.swift new file mode 100644 index 0000000..4318fd0 --- /dev/null +++ b/echog/echog/Data/Model/MyPage.swift @@ -0,0 +1,46 @@ +// +// MyPage.swift +// echog +// +// Created by minsong kim on 12/26/24. +// + +import UIKit + +enum MyPageList: CaseIterable { + case myVoteList + case reportList + + var title: String { + switch self { + case .myVoteList: + "내 투표 리스트" + case .reportList: + "신고 리스트" + } + } +} + +enum MyPageSignOut: CaseIterable { + case logOut + case signOut + + var title: String { + switch self { + case .logOut: + "로그아웃" + case .signOut: + "회원탈퇴" + } + } + + var color: UIColor { + switch self { + case .logOut: + .black + case .signOut: + .red + } + } +} + diff --git a/echog/echog/Model/UserDTO.swift b/echog/echog/Data/Model/UserDTO.swift similarity index 100% rename from echog/echog/Model/UserDTO.swift rename to echog/echog/Data/Model/UserDTO.swift diff --git a/echog/echog/Design System/Cell/VoteCell.swift b/echog/echog/Design System/Cell/VoteCell.swift new file mode 100644 index 0000000..9324712 --- /dev/null +++ b/echog/echog/Design System/Cell/VoteCell.swift @@ -0,0 +1,91 @@ +// +// VoteCell.swift +// echog +// +// Created by minsong kim on 1/8/25. +// + +import UIKit +import SnapKit + +class VoteCell: UITableViewCell { + static let identifier = "VoteCell" + + private let titleLabel: UILabel = { + let label = UILabel() + label.font = .semiboldTitle15 + + return label + }() + + private let contentsLabel: UILabel = { + let label = UILabel() + label.font = .mediumTitle14 + label.textColor = .grayscale70 + + return label + }() + + private let dateLabel: UILabel = { + let label = UILabel() + label.font = .regularTitle13 + label.textColor = .grayscale50Caption + + return label + }() + + private let dateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "ko_KR") + dateFormatter.dateFormat = "MM월 dd일 E요일" + + return dateFormatter + }() + + private let voteNumberLabel: UILabel = { + let label = UILabel() + label.font = .regularTitle13 + label.textColor = .grayscale50Caption + label.textAlignment = .right + + return label + }() + + private func configureLabels() { + self.addSubview(titleLabel) + self.addSubview(contentsLabel) + self.addSubview(dateLabel) + self.addSubview(voteNumberLabel) + + titleLabel.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(16) + make.top.equalToSuperview().inset(16) + make.height.equalTo(22) + } + + contentsLabel.snp.makeConstraints { make in + make.top.equalTo(titleLabel.snp.bottom) + make.leading.trailing.equalTo(titleLabel) + make.height.equalTo(20) + } + + dateLabel.snp.makeConstraints { make in + make.leading.trailing.equalTo(titleLabel) + make.bottom.equalToSuperview().inset(16) + } + + voteNumberLabel.snp.makeConstraints { make in + make.leading.trailing.equalTo(titleLabel) + make.bottom.equalTo(dateLabel) + } + } + + func configureCells(title: String, contents: String, date: Date, voteNumber: Int) { + titleLabel.text = title + contentsLabel.text = contents + dateLabel.text = dateFormatter.string(from: date) + voteNumberLabel.text = "\(voteNumber)명 투표" + + configureLabels() + } +} diff --git a/echog/echog/Design System/Compoment/UnderlineSegmentedControl.swift b/echog/echog/Design System/Compoment/UnderlineSegmentedControl.swift new file mode 100644 index 0000000..dab2a85 --- /dev/null +++ b/echog/echog/Design System/Compoment/UnderlineSegmentedControl.swift @@ -0,0 +1,68 @@ +// +// UnderlineSegmentedControl.swift +// echog +// +// Created by minsong kim on 1/8/25. +// + +import UIKit + +class UnderlineSegmentedControl: UISegmentedControl { + private lazy var underlineBackView: UIView = { + let width = self.bounds.width + let height = 2.0 + let xPosition = CGFloat(self.selectedSegmentIndex * Int(width)) + let yPosition = self.bounds.size.height - 1.0 + let frame = CGRect(x: xPosition, y: yPosition, width: width, height: height) + let view = UIView(frame: frame) + + view.backgroundColor = .grayscale40Disabled + self.addSubview(view) + + return view + }() + + private lazy var underlineView: UIView = { + let width = self.bounds.width / CGFloat(self.numberOfSegments) + let height = 2.0 + let xPosition = CGFloat(self.selectedSegmentIndex * Int(width)) + let yPosition = self.bounds.size.height - 1.0 + let frame = CGRect(x: xPosition, y: yPosition, width: width, height: height) + let view = UIView(frame: frame) + + view.backgroundColor = .black + self.addSubview(view) + + return view + }() + + override init(items: [Any]?) { + super.init(items: items) + + self.removeDivider() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + let underlineFinalXPosition = (self.bounds.width / CGFloat(self.numberOfSegments)) * CGFloat(self.selectedSegmentIndex) + + UIView.animate(withDuration: 0.1) { + self.underlineView.frame.origin.x = underlineFinalXPosition + } + } + + private func removeDivider() { + let image = UIImage() + + self.setBackgroundImage(image, for: .normal, barMetrics: .default) + self.setBackgroundImage(image, for: .selected, barMetrics: .default) + self.setBackgroundImage(image, for: .highlighted, barMetrics: .default) + + self.setDividerImage(image, forLeftSegmentState: .selected, rightSegmentState: .normal, barMetrics: .default) + } +} diff --git a/echog/echog/Presentation/MyPage/MyPageViewController.swift b/echog/echog/Presentation/MyPage/MyPageViewController.swift new file mode 100644 index 0000000..79b6fcc --- /dev/null +++ b/echog/echog/Presentation/MyPage/MyPageViewController.swift @@ -0,0 +1,122 @@ +// +// MyPageViewController.swift +// echog +// +// Created by minsong kim on 12/26/24. +// + +import UIKit +import SnapKit + +class MyPageViewController: UIViewController { + private let titleLabel: UILabel = { + let label = UILabel() + label.font = .semiboldHeadline24 + label.textColor = .black + label.text = "마이페이지" + + return label + }() + + private let closeButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "xmark"), for: .normal) + button.tintColor = .black + + return button + }() + + private let tableView: UITableView = { + let table = UITableView() + table.backgroundColor = .clear + table.separatorStyle = .none + + return table + }() + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + + configureBar() + configureTableView() + } + + private func configureBar() { + view.addSubview(titleLabel) + view.addSubview(closeButton) + + titleLabel.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview().inset(20) + make.top.equalToSuperview().inset(100) + make.height.equalTo(34) + } + + closeButton.snp.makeConstraints { make in + make.centerY.equalTo(titleLabel.snp.centerY) + make.trailing.equalToSuperview().inset(24) + make.width.height.equalTo(24) + } + } + + private func configureTableView() { + tableView.dataSource = self + tableView.delegate = self + tableView.sectionHeaderTopPadding = 0 + + view.addSubview(tableView) + + tableView.snp.makeConstraints { make in + make.top.equalTo(titleLabel.snp.bottom).offset(16) + make.leading.trailing.equalToSuperview() + make.bottom.equalToSuperview() + } + } +} + +extension MyPageViewController: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + 2 + } + + func numberOfSections(in tableView: UITableView) -> Int { + 2 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = UITableViewCell(style: .default, reuseIdentifier: "MyPageListCell") + var content = cell.defaultContentConfiguration() + if indexPath.section == 0 { + content.text = MyPageList.allCases[indexPath.item].title + } else { + content.text = MyPageSignOut.allCases[indexPath.item].title + content.textProperties.color = MyPageSignOut.allCases[indexPath.item].color + } + + content.textProperties.font = .mediumTitle15 + cell.contentConfiguration = content + + return cell + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let line = UIView(frame: CGRect(x: 0, y:0, width: tableView.frame.width, height: 1)) + line.backgroundColor = .grayscale30 + + return line + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + 1 + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + 60 + } +} + +//#Preview { +// let vc = MyPageViewController() +// +// return vc +//} diff --git a/echog/echog/Presentation/MyPage/MyVoteListViewController.swift b/echog/echog/Presentation/MyPage/MyVoteListViewController.swift new file mode 100644 index 0000000..7da80f9 --- /dev/null +++ b/echog/echog/Presentation/MyPage/MyVoteListViewController.swift @@ -0,0 +1,128 @@ +// +// MyVoteListViewController.swift +// echog +// +// Created by minsong kim on 12/26/24. +// + +import UIKit +import SnapKit + +class MyVoteListViewController: UIViewController { + private let backButton: UIButton = { + let button = UIButton() + button.setImage(UIImage(systemName: "arrow.backward"), for: .normal) + button.tintColor = .black + + return button + }() + + private let titleLabel: UILabel = { + let label = UILabel() + label.text = "내 투표 리스트" + label.font = .semiboldLargetitle17 + label.textColor = .black + + return label + }() + + private let segmentedControl = UnderlineSegmentedControl(items: ["진행 중인 투표","완료된 투표"]) + + private let tableView: UITableView = { + let table = UITableView() + table.separatorStyle = .singleLine + table.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + + return table + }() + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + + configureBar() + configureVote() + configureTableView() + } + + private func configureBar() { + view.addSubview(backButton) + view.addSubview(titleLabel) + + backButton.snp.makeConstraints { make in + make.height.equalTo(28) + make.width.equalTo(18) + make.leading.equalToSuperview().inset(24) + make.top.equalToSuperview().inset(80) + } + + titleLabel.snp.makeConstraints { make in + make.centerY.equalTo(backButton.snp.centerY) + make.leading.equalTo(backButton.snp.trailing).offset(8) + make.trailing.equalToSuperview().inset(24) + } + } + + private func configureVote() { + view.addSubview(segmentedControl) + + segmentedControl.snp.makeConstraints { make in + make.top.equalTo(titleLabel.snp.bottom).offset(20) + make.leading.trailing.equalToSuperview() + make.height.equalTo(50) + } + } + + private func configureTableView() { + tableView.dataSource = self + tableView.delegate = self + tableView.register(VoteCell.self, forCellReuseIdentifier: VoteCell.identifier) + tableView.sectionHeaderTopPadding = 0 + + view.addSubview(tableView) + + tableView.snp.makeConstraints { make in + make.top.equalTo(segmentedControl.snp.bottom) + make.leading.trailing.equalToSuperview() + make.bottom.equalToSuperview() + } + } +} + +extension MyVoteListViewController: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + 10 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: VoteCell.identifier, for: indexPath) as? VoteCell else { + return UITableViewCell() + } + + cell.configureCells(title: "점메츄", contents: "점심메뉴 뭐먹지", date: Date(), voteNumber: 5) + + return cell + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let line = UIView(frame: CGRect(x: 0, y:0, width: tableView.frame.width, height: 1)) + line.backgroundColor = .grayscale30 + + return line + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + 1 + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + 100 + } + +} + +#Preview { + let vc = MyVoteListViewController() + + return vc +}