Skip to content

Commit 31a247d

Browse files
authored
[Feat-T3-201] 제보 히스토리 화면 구현 및 화면 연결 (완료) (#70)
* feat: 제보하기 히스토리 화면 구현(완료) * fix: 마이페이지 네비게이션 수정
1 parent 090a6d4 commit 31a247d

19 files changed

Lines changed: 299 additions & 116 deletions

Projects/Presentation/Sources/Common/Extension/UIViewController+.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ extension UIViewController {
1414
case withBackButton(title: String) // 백버튼 + 타이틀 (없으면 빈 String)
1515
case withTitle(title: String) // 오로지 타이틀만
1616
case withProgressBar(step: Int) // 백버튼 + progress
17+
case withRightButton(
18+
title: String,
19+
rightButtonImage: UIImage,
20+
rightButtonAction: UIAction,
21+
rightButtonTintColor: UIColor? = nil,
22+
withBackButton: Bool = false) // 커스텀 우측 버튼
1723
}
1824

1925
func configureCustomNavigationBar(navigationBarStyle: NavigationBarStyle, backgroundColor: UIColor? = .white) {
@@ -31,6 +37,7 @@ extension UIViewController {
3137
private func customNavigationBar(navigationBarStyle: NavigationBarStyle) -> UIView {
3238
let navigationBar = UIView()
3339
let customBackButton = UIButton()
40+
let customRightButton = UIButton()
3441
let titleLabel = UILabel()
3542
let progressView = UIImageView()
3643

@@ -46,6 +53,7 @@ extension UIViewController {
4653
titleLabel.textColor = BitnagilColor.gray10
4754

4855
progressView.isHidden = true
56+
customRightButton.isHidden = true
4957

5058
switch navigationBarStyle {
5159
case .withBackButton(let title):
@@ -59,10 +67,23 @@ extension UIViewController {
5967
titleLabel.isHidden = true
6068
progressView.image = progressImage(step: step)
6169
progressView.isHidden = false
70+
case .withRightButton(
71+
title: let title,
72+
rightButtonImage: let rightButtonImage,
73+
rightButtonAction: let action,
74+
rightButtonTintColor: let tintColor,
75+
withBackButton: let withBackButton):
76+
77+
customBackButton.isHidden = !withBackButton
78+
customRightButton.isHidden = false
79+
customRightButton.addAction(action, for: .touchUpInside)
80+
titleLabel.text = title
81+
customRightButton.setImage(rightButtonImage, for: .normal)
82+
customRightButton.tintColor = tintColor
6283
}
6384

6485
navigationBar.backgroundColor = .systemBackground
65-
[customBackButton, titleLabel, progressView].forEach {
86+
[customBackButton, titleLabel, progressView, customRightButton].forEach {
6687
navigationBar.addSubview($0)
6788
}
6889

@@ -72,6 +93,12 @@ extension UIViewController {
7293
make.size.equalTo(48)
7394
}
7495

96+
customRightButton.snp.makeConstraints { make in
97+
make.top.equalToSuperview()
98+
make.trailing.equalToSuperview()
99+
make.size.equalTo(48)
100+
}
101+
75102
titleLabel.snp.makeConstraints { make in
76103
make.center.equalToSuperview()
77104
}

Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,16 @@ public struct PresentationDependencyAssembler: DependencyAssemblerProtocol {
121121
return WithdrawViewModel(authRepository: authRepository)
122122
}
123123

124-
DIContainer.shared.register(type: ReportViewModel.self) { container in
124+
DIContainer.shared.register(type: ReportRegistrationViewModel.self) { container in
125125
guard let reportUseCase = container.resolve(type: ReportUseCaseProtocol.self)
126126
else { fatalError("reportUseCase 의존성이 등록되지 않았습니다.") }
127127

128-
return ReportViewModel(reportUseCase: reportUseCase)
128+
return ReportRegistrationViewModel(reportUseCase: reportUseCase)
129129
}
130130

131131
DIContainer.shared
132-
.register(type: ReportListHistoryViewModel.self) { container in
133-
return ReportListHistoryViewModel()
132+
.register(type: ReportHistoryViewModel.self) { container in
133+
return ReportHistoryViewModel()
134134
}
135135

136136
DIContainer.shared.register(type: ReportDetailViewModel.self) { container in

Projects/Presentation/Sources/MyPage/View/MypageView.swift

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ final class MypageView: BaseViewController<MypageViewModel> {
1414
private enum Layout {
1515
static let profileImageViewSize: CGFloat = 80
1616
static let profileImageViewCornerRadius: CGFloat = profileImageViewSize / 2
17-
static let profileImageViewTopSpacing: CGFloat = 32
17+
static let profileImageViewTopSpacing: CGFloat = 74
1818
static let nicknameLabelHeight: CGFloat = 24
1919
static let nicknameLabelTopSpacing: CGFloat = 12
2020
static let divideLineHeight: CGFloat = 6
@@ -41,28 +41,12 @@ final class MypageView: BaseViewController<MypageViewModel> {
4141

4242
override func viewWillAppear(_ animated: Bool) {
4343
super.viewWillAppear(animated)
44-
45-
navigationController?.setNavigationBarHidden(false, animated: animated)
46-
47-
let appearance = UINavigationBarAppearance()
48-
appearance.configureWithOpaqueBackground()
49-
appearance.backgroundColor = .white
50-
appearance.shadowColor = .clear
51-
52-
navigationController?.navigationBar.standardAppearance = appearance
53-
navigationController?.navigationBar.scrollEdgeAppearance = appearance
54-
navigationController?.navigationBar.compactAppearance = appearance
55-
navigationController?.navigationBar.isHidden = false
44+
navigationController?.navigationBar.isHidden = true
5645
}
5746

5847
override func configureAttribute() {
5948
view.backgroundColor = .white
60-
navigationItem.rightBarButtonItem = settingButton
61-
title = "마이페이지"
6249

63-
settingButton.action = #selector(settingButtonTapped)
64-
settingButton.target = self
65-
settingButton.image = BitnagilIcon.settingIcon?.withRenderingMode(.alwaysOriginal)
6650
profileImageView.image = BitnagilGraphic.profileGraphic
6751

6852
nicknameLabel.font = BitnagilFont(style: .title3, weight: .semiBold).font
@@ -76,6 +60,21 @@ final class MypageView: BaseViewController<MypageViewModel> {
7660
tableView.delegate = self
7761
tableView.separatorStyle = .none
7862
tableView.bounces = false
63+
64+
let settingButtonImage = BitnagilIcon.settingIcon
65+
let settingButtonAction = UIAction { [weak self] _ in
66+
guard let settingViewModel = Shared.DIContainer.shared.resolve(type: SettingViewModel.self) else { return }
67+
let settingView = SettingView(viewModel: settingViewModel)
68+
self?.navigationController?.pushViewController(settingView, animated: true)
69+
}
70+
71+
configureCustomNavigationBar(navigationBarStyle:
72+
.withRightButton(
73+
title: "마이페이지",
74+
rightButtonImage: settingButtonImage ?? .init(),
75+
rightButtonAction: settingButtonAction,
76+
rightButtonTintColor: BitnagilColor.gray70,
77+
withBackButton: false))
7978
}
8079

8180
override func configureLayout() {
@@ -126,12 +125,6 @@ final class MypageView: BaseViewController<MypageViewModel> {
126125
}
127126
.store(in: &cancellables)
128127
}
129-
130-
@objc private func settingButtonTapped() {
131-
guard let settingViewModel = Shared.DIContainer.shared.resolve(type: SettingViewModel.self) else { return }
132-
let settingView = SettingView(viewModel: settingViewModel)
133-
navigationController?.pushViewController(settingView, animated: true)
134-
}
135128
}
136129

137130
extension MypageView: UITableViewDelegate {
@@ -165,16 +158,24 @@ extension MypageView: UITableViewDataSource {
165158

166159
let selectedMenu = MypageViewModel.MypageMenu.allCases[indexPath.row]
167160

168-
guard selectedMenu == .resetGoal else {
169-
viewModel.action(input: .didSelectMenu(menu: selectedMenu))
170-
return
171-
}
161+
switch selectedMenu {
172162

173-
guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self)
174-
else { fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.") }
163+
case .reportHistory:
164+
guard let reportHistoryViewModel = DIContainer.shared.resolve(type: ReportHistoryViewModel.self)
165+
else { fatalError("reportHistoryViewModel 의존성이 등록되지 않았습니다.") }
175166

176-
let onboardingView = OnboardingResultViewController(viewModel: onboardingViewModel, entryPoint: .myPagePrevious)
177-
onboardingView.hidesBottomBarWhenPushed = true
178-
navigationController?.pushViewController(onboardingView, animated: true)
167+
let reportHistoryViewController = ReportHistoryViewController(viewModel: reportHistoryViewModel)
168+
reportHistoryViewController.hidesBottomBarWhenPushed = true
169+
navigationController?.pushViewController(reportHistoryViewController, animated: true)
170+
case .resetGoal:
171+
guard let onboardingViewModel = DIContainer.shared.resolve(type: OnboardingViewModel.self)
172+
else { fatalError("onboardingViewModel 의존성이 등록되지 않았습니다.") }
173+
174+
let onboardingView = OnboardingResultViewController(viewModel: onboardingViewModel, entryPoint: .myPagePrevious)
175+
onboardingView.hidesBottomBarWhenPushed = true
176+
navigationController?.pushViewController(onboardingView, animated: true)
177+
case .notice, .faq:
178+
viewModel.action(input: .didSelectMenu(menu: selectedMenu))
179+
}
179180
}
180181
}

Projects/Presentation/Sources/MyPage/ViewModel/MypageViewModel.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ final class MypageViewModel: ViewModel {
2121
}
2222

2323
enum MypageMenu: String, CaseIterable {
24+
case reportHistory = "내 제보 기록"
2425
case resetGoal = "내 목표 재설정"
2526
case notice = "공지사항"
2627
case faq = "자주 묻는 질문"
@@ -67,6 +68,8 @@ final class MypageViewModel: ViewModel {
6768
if let url = URL(string: "https://complex-wombat-99f.notion.site/23ff4587491d80659ae3ea392afbc05e") {
6869
externalURLPublisher.send(url)
6970
}
71+
case .reportHistory:
72+
break
7073
}
7174
}
7275
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// ReportProgress+.swift
3+
// Presentation
4+
//
5+
// Created by 이동현 on 11/20/25.
6+
//
7+
8+
import Domain
9+
import UIKit
10+
11+
extension ReportProgress {
12+
var backgroundColor: UIColor? {
13+
switch self {
14+
case .received:
15+
BitnagilColor.green10
16+
case .inProgress:
17+
BitnagilColor.skyblue10
18+
case .completed:
19+
BitnagilColor.gray95
20+
case .entire:
21+
nil
22+
}
23+
}
24+
25+
var titleColor: UIColor? {
26+
switch self {
27+
case .received:
28+
BitnagilColor.green500
29+
case .inProgress:
30+
BitnagilColor.blue300
31+
case .completed:
32+
BitnagilColor.gray40
33+
case .entire:
34+
nil
35+
}
36+
}
37+
}

Projects/Presentation/Sources/Report/View/Component/ReportCategoryTableViewCell.swift renamed to Projects/Presentation/Sources/Report/View/Component/Common/ReportCategoryTableViewCell.swift

File renamed without changes.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// ReportProgressView.swift
3+
// Presentation
4+
//
5+
// Created by 이동현 on 11/20/25.
6+
//
7+
8+
import Domain
9+
import SnapKit
10+
import UIKit
11+
12+
final class ReportProgressView: UIView {
13+
14+
private enum Layout {
15+
static let progressViewHeight: CGFloat = 26
16+
static let progressLabelHeight: CGFloat = 18
17+
static let progressLabelHorizontalSpacing: CGFloat = 10
18+
static let progressLabelVerticalSpacing: CGFloat = 4
19+
}
20+
21+
private let progressLabel = UILabel()
22+
23+
override init(frame: CGRect) {
24+
super.init(frame: frame)
25+
configureAttribute()
26+
configureLayout()
27+
}
28+
29+
required init?(coder: NSCoder) {
30+
fatalError("init(coder:) has not been implemented")
31+
}
32+
33+
private func configureAttribute() {
34+
layer.cornerRadius = 6
35+
layer.masksToBounds = true
36+
37+
progressLabel.font = BitnagilFont.init(style: .caption1, weight: .semiBold).font
38+
}
39+
40+
private func configureLayout() {
41+
addSubview(progressLabel)
42+
43+
self.snp.makeConstraints { make in
44+
make.height.equalTo(Layout.progressViewHeight)
45+
}
46+
47+
progressLabel.snp.makeConstraints { make in
48+
make.height.equalTo(Layout.progressLabelHeight)
49+
50+
make.verticalEdges
51+
.equalToSuperview()
52+
.inset(Layout.progressLabelVerticalSpacing)
53+
54+
make.horizontalEdges
55+
.equalToSuperview()
56+
.inset(Layout.progressLabelHorizontalSpacing)
57+
}
58+
}
59+
60+
func configure(with progress: ReportProgress) {
61+
backgroundColor = progress.backgroundColor
62+
63+
progressLabel.textColor = progress.titleColor
64+
progressLabel.text = progress.description
65+
}
66+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//
2+
// ReportHistoryEmptyView.swift
3+
// Presentation
4+
//
5+
// Created by 이동현 on 11/20/25.
6+
//
7+
8+
import SnapKit
9+
import UIKit
10+
11+
final class ReportHistoryEmptyView: UIView {
12+
private enum Layout {
13+
static let semiBoldLabelHeight: CGFloat = 28
14+
static let regularLabelHeight: CGFloat = 20
15+
static let stackViewSpacing: CGFloat = 2
16+
static let stackViewHeight: CGFloat = 50
17+
static let stackViewWidth: CGFloat = 269
18+
}
19+
20+
private let labelStackView = UIStackView()
21+
private let semiBoldLabel = UILabel()
22+
private let regularLabel = UILabel()
23+
24+
override init(frame: CGRect) {
25+
super.init(frame: frame)
26+
27+
configureAttribute()
28+
configureLayout()
29+
}
30+
31+
required init?(coder: NSCoder) {
32+
fatalError("init(coder:) has not been implemented")
33+
}
34+
35+
private func configureAttribute() {
36+
backgroundColor = BitnagilColor.gray99
37+
38+
labelStackView.axis = .vertical
39+
labelStackView.alignment = .center
40+
41+
semiBoldLabel.font = BitnagilFont.init(style: .subtitle1, weight: .semiBold).font
42+
semiBoldLabel.textColor = BitnagilColor.gray30
43+
semiBoldLabel.text = "제보한 내역이 없어요."
44+
45+
regularLabel.font = BitnagilFont.init(style: .body2, weight: .regular).font
46+
regularLabel.textColor = BitnagilColor.gray70
47+
regularLabel.text = "원하는 카테고리로 제보를 시작해보세요."
48+
}
49+
50+
private func configureLayout() {
51+
addSubview(labelStackView)
52+
labelStackView.addArrangedSubview(semiBoldLabel)
53+
labelStackView.addArrangedSubview(regularLabel)
54+
55+
labelStackView.snp.makeConstraints { make in
56+
make.center.equalToSuperview()
57+
make.height.equalTo(Layout.stackViewHeight)
58+
make.width.equalTo(Layout.stackViewWidth)
59+
}
60+
61+
semiBoldLabel.snp.makeConstraints { make in
62+
make.height.equalTo(Layout.semiBoldLabelHeight)
63+
}
64+
65+
regularLabel.snp.makeConstraints { make in
66+
make.height.equalTo(Layout.regularLabelHeight)
67+
}
68+
}
69+
}

Projects/Presentation/Sources/Report/View/Component/ReportHistoryTableHeaderView.swift renamed to Projects/Presentation/Sources/Report/View/Component/ReportHistory/ReportHistoryTableHeaderView.swift

File renamed without changes.

0 commit comments

Comments
 (0)