Skip to content

Commit a0caab1

Browse files
committed
feat: 제보하기 화면 구현
1 parent 1f1a036 commit a0caab1

18 files changed

Lines changed: 1347 additions & 2 deletions
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// ReportType.swift
3+
// Domain
4+
//
5+
// Created by 이동현 on 11/9/25.
6+
//
7+
8+
public enum ReportType: String, CaseIterable {
9+
case lamp
10+
case road
11+
case etc
12+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// RoutineCompletionEntity.swift
3+
// Domain
4+
//
5+
// Created by 최정인 on 8/6/25.
6+
//
7+
8+
public struct RoutineCompletionEntity {
9+
public let performedDate: String
10+
public let routineId: String
11+
public let completeYn: Bool
12+
public let historySeq: Int
13+
public let routineType: String
14+
15+
public init(
16+
performedDate: String,
17+
routineId: String,
18+
completeYn: Bool,
19+
historySeq: Int,
20+
routineType: String
21+
) {
22+
self.performedDate = performedDate
23+
self.routineId = routineId
24+
self.completeYn = completeYn
25+
self.historySeq = historySeq
26+
self.routineType = routineType
27+
}
28+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//
2+
// RequiredTitleLabel.swift
3+
// Presentation
4+
//
5+
// Created by 이동현 on 11/8/25.
6+
//
7+
8+
import SnapKit
9+
import UIKit
10+
11+
final class RequiredTitleLabel: UIView {
12+
private enum Layout {
13+
static let asteriskLeadingSpacing: CGFloat = 3
14+
}
15+
16+
private let titleLabel = UILabel()
17+
private let asteriskLabel = UILabel()
18+
19+
init(title: String) {
20+
super.init(frame: .zero)
21+
configureAttribute(title: title)
22+
configureLayout()
23+
}
24+
25+
required init?(coder: NSCoder) {
26+
fatalError("init(coder:) has not been implemented")
27+
}
28+
29+
private func configureAttribute(title: String) {
30+
titleLabel.font = BitnagilFont.init(
31+
style: .body2,
32+
weight: .semiBold
33+
).font
34+
titleLabel.textColor = BitnagilColor.gray10
35+
titleLabel.text = title
36+
37+
asteriskLabel.text = "*"
38+
asteriskLabel.font = BitnagilFont.init(
39+
style: .body1,
40+
weight: .semiBold
41+
).font
42+
asteriskLabel.textColor = BitnagilColor.error
43+
}
44+
45+
private func configureLayout() {
46+
addSubview(titleLabel)
47+
addSubview(asteriskLabel)
48+
49+
titleLabel.snp.makeConstraints { make in
50+
make.verticalEdges.leading.equalToSuperview()
51+
}
52+
53+
asteriskLabel.snp.makeConstraints { make in
54+
make.leading
55+
.equalTo(titleLabel.snp.trailing)
56+
.offset(Layout.asteriskLeadingSpacing)
57+
58+
make.top.equalTo(titleLabel)
59+
}
60+
}
61+
}

Projects/Presentation/Sources/Common/Component/SelectableItemTableView.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@ final class SelectableItemTableView<T: SelectableItem & CaseIterable & Equatable
1919

2020
private let itemTableView = UITableView()
2121
private let items: [T]
22+
private let markIsSelected: Bool
2223
private var selectedItem: T? {
2324
didSet {
25+
if !markIsSelected && selectedItem == nil { return }
2426
delegate?.selectableItemTableView(self, didSelectItem: selectedItem)
2527
}
2628
}
2729

2830
weak var delegate: SelectableItemTableViewDelegate?
2931

30-
init(items: [T], selectedItem: T? = nil) {
32+
init(items: [T], selectedItem: T? = nil, markIsSelected: Bool = true) {
3133
self.items = items.sorted(by: { $0.id < $1.id })
3234
self.selectedItem = selectedItem
35+
self.markIsSelected = markIsSelected
3336
super.init(nibName: nil, bundle: nil)
3437
}
3538

@@ -93,6 +96,9 @@ final class SelectableItemTableView<T: SelectableItem & CaseIterable & Equatable
9396
} else {
9497
self.selectedItem = selectedItem
9598
}
99+
100+
if !markIsSelected { self.selectedItem = nil }
101+
96102
itemTableView.reloadData()
97103
dismiss(animated: true)
98104
}

Projects/Presentation/Sources/Common/DesignSystem/BitnagilIcon.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ enum BitnagilIcon {
7272
static let settingIcon = UIImage(named: "setting_icon", in: bundle, with: nil)
7373
static let exclamationFilledIcon = UIImage(named: "exclamation_filled_icon", in: bundle, with: nil)
7474

75+
// MARK: - Report
76+
static let roundDeleteIcon = UIImage(named: "round_delete_icon", in: bundle, with: nil)
77+
static let cameraIcon = UIImage(named: "camera_icon", in: bundle, with: nil)
78+
static let locationIcon = UIImage(named: "location_icon", in: bundle, with: nil)
79+
7580
// MARK: - Routine Creation Icons
7681
static let asteriskIcon = UIImage(named: "asterisk_icon", in: bundle, with: nil)
7782
static let deleteIcon = UIImage(named: "delete_icon", in: bundle, with: nil)

Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,9 @@ public struct PresentationDependencyAssembler: DependencyAssemblerProtocol {
120120

121121
return WithdrawViewModel(authRepository: authRepository)
122122
}
123+
124+
DIContainer.shared.register(type: ReportViewModel.self) { container in
125+
return ReportViewModel()
126+
}
123127
}
124128
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//
2+
// RoutineDeleteAlertView.swift
3+
// Presentation
4+
//
5+
// Created by 최정인 on 8/4/25.
6+
//
7+
8+
import SnapKit
9+
import UIKit
10+
11+
protocol RoutineDeleteAlertViewDelegate: AnyObject {
12+
func routineDeleteAlertViewDidTapDeleteAllRoutine(_ sender: RoutineDeleteAlertView)
13+
func routineDeleteAlertViewDidTapDeleteDailyRoutine(_ sender: RoutineDeleteAlertView)
14+
}
15+
16+
final class RoutineDeleteAlertView: UIView {
17+
private enum Layout {
18+
static let deleteLabelHeight: CGFloat = 48
19+
static let deleteLabelTopSpacing: CGFloat = 23
20+
static let buttonHorizontalMargin: CGFloat = 23
21+
static let buttonHeight: CGFloat = 44
22+
static let deleteDailyRoutineButtonTopSpacing: CGFloat = 22
23+
static let deleteAllRoutineButtonTopSpacing: CGFloat = 10
24+
}
25+
26+
private let contentView = UIView()
27+
private let deleteLabel = UILabel()
28+
private let deleteDailyRoutineButton = UIButton()
29+
private let deleteAllRoutineButton = UIButton()
30+
weak var delegate: RoutineDeleteAlertViewDelegate?
31+
32+
init() {
33+
super.init(frame: .zero)
34+
configureAttribute()
35+
configureLayout()
36+
}
37+
38+
required init?(coder: NSCoder) {
39+
fatalError("init(coder:) has not been implemented")
40+
}
41+
42+
private func configureAttribute() {
43+
contentView.backgroundColor = .white
44+
contentView.layer.masksToBounds = true
45+
contentView.layer.cornerRadius = 20
46+
47+
deleteLabel.text = "해당 루틴은\n반복 루틴으로 설정되어 있어요"
48+
deleteLabel.numberOfLines = 2
49+
deleteLabel.textAlignment = .center
50+
deleteLabel.font = BitnagilFont(style: .body1, weight: .semiBold).font
51+
deleteLabel.textColor = BitnagilColor.gray10
52+
53+
var deleteDailyRoutineButtonConfiguration = UIButton.Configuration.filled()
54+
deleteDailyRoutineButtonConfiguration.baseBackgroundColor = .white
55+
deleteDailyRoutineButtonConfiguration.background.cornerRadius = 8
56+
deleteDailyRoutineButtonConfiguration.attributedTitle = AttributedString(
57+
"당일만 삭제",
58+
attributes: .init([.font: BitnagilFont(style: .body2, weight: .medium).font]))
59+
deleteDailyRoutineButtonConfiguration.baseForegroundColor = BitnagilColor.navy500
60+
deleteDailyRoutineButtonConfiguration.background.strokeColor = BitnagilColor.navy500
61+
deleteDailyRoutineButtonConfiguration.background.strokeWidth = 1
62+
deleteDailyRoutineButton.configuration = deleteDailyRoutineButtonConfiguration
63+
deleteDailyRoutineButton.addAction(
64+
UIAction { [weak self] _ in
65+
guard let self else { return }
66+
self.delegate?.routineDeleteAlertViewDidTapDeleteDailyRoutine(self)
67+
},
68+
for: .touchUpInside)
69+
70+
var deleteAllRoutineButtonConfiguration = UIButton.Configuration.filled()
71+
deleteAllRoutineButtonConfiguration.baseBackgroundColor = .white
72+
deleteAllRoutineButtonConfiguration.background.cornerRadius = 8
73+
deleteAllRoutineButtonConfiguration.attributedTitle = AttributedString(
74+
"전체 루틴 삭제",
75+
attributes: .init([.font: BitnagilFont(style: .body2, weight: .medium).font]))
76+
deleteAllRoutineButtonConfiguration.baseForegroundColor = BitnagilColor.navy500
77+
deleteAllRoutineButtonConfiguration.background.strokeColor = BitnagilColor.navy500
78+
deleteAllRoutineButtonConfiguration.background.strokeWidth = 1
79+
deleteAllRoutineButton.configuration = deleteAllRoutineButtonConfiguration
80+
deleteAllRoutineButton.addAction(
81+
UIAction { [weak self] _ in
82+
guard let self else { return }
83+
self.delegate?.routineDeleteAlertViewDidTapDeleteAllRoutine(self)
84+
},
85+
for: .touchUpInside)
86+
}
87+
88+
private func configureLayout() {
89+
addSubview(contentView)
90+
91+
contentView.snp.makeConstraints { make in
92+
make.edges.equalToSuperview()
93+
}
94+
95+
[deleteLabel, deleteDailyRoutineButton, deleteAllRoutineButton].forEach {
96+
contentView.addSubview($0)
97+
}
98+
99+
deleteLabel.snp.makeConstraints { make in
100+
make.height.equalTo(Layout.deleteLabelHeight)
101+
make.centerX.equalToSuperview()
102+
make.top.equalToSuperview().offset(Layout.deleteLabelTopSpacing)
103+
}
104+
105+
deleteDailyRoutineButton.snp.makeConstraints { make in
106+
make.top.equalTo(deleteLabel.snp.bottom).offset(Layout.deleteDailyRoutineButtonTopSpacing)
107+
make.leading.equalToSuperview().offset(Layout.buttonHorizontalMargin)
108+
make.trailing.equalToSuperview().inset(Layout.buttonHorizontalMargin)
109+
make.height.equalTo(Layout.buttonHeight)
110+
}
111+
112+
deleteAllRoutineButton.snp.makeConstraints { make in
113+
make.top.equalTo(deleteDailyRoutineButton.snp.bottom).offset(Layout.deleteAllRoutineButtonTopSpacing)
114+
make.leading.equalToSuperview().offset(Layout.buttonHorizontalMargin)
115+
make.trailing.equalToSuperview().inset(Layout.buttonHorizontalMargin)
116+
make.height.equalTo(Layout.buttonHeight)
117+
}
118+
}
119+
120+
private func scrollCollectionViewToEnd() {
121+
122+
}
123+
}

Projects/Presentation/Sources/Home/ViewModel/HomeViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class HomeViewModel: ViewModel {
3535
let updateVersionPublisher: AnyPublisher<URL?, Never>
3636
}
3737

38-
private(set) var output: Output
38+
let output: Output
3939
private var routines: [String: [Routine]] = [:]
4040
private var routinesCompleted: [String: Bool] = [:]
4141
private let nicknameSubject = CurrentValueSubject<String, Never>("")
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// PhotoItem.swift
3+
// Presentation
4+
//
5+
// Created by 이동현 on 11/9/25.
6+
//
7+
8+
import Foundation
9+
10+
public struct PhotoItem: Hashable {
11+
public let id: UUID
12+
public let data: Data
13+
14+
public init(id: UUID = .init(), data: Data) {
15+
self.id = id
16+
self.data = data
17+
}
18+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// ReportType+.swift
3+
// Presentation
4+
//
5+
// Created by 이동현 on 11/9/25.
6+
//
7+
8+
import Domain
9+
10+
extension ReportType: SelectableItem {
11+
var id: Int {
12+
switch self {
13+
case .lamp:
14+
return 0
15+
case .road:
16+
return 1
17+
case .etc:
18+
return 2
19+
}
20+
}
21+
22+
var displayName: String? {
23+
return nil
24+
}
25+
26+
var description: String {
27+
switch self {
28+
case .lamp:
29+
return "가로등"
30+
case .road:
31+
return "도로"
32+
case .etc:
33+
return "기타"
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)