-
Notifications
You must be signed in to change notification settings - Fork 1
[Feat-T3-195] 제보하기 화면 구현 #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // | ||
| // ReportType.swift | ||
| // Domain | ||
| // | ||
| // Created by 이동현 on 11/9/25. | ||
| // | ||
|
|
||
| public enum ReportType: String, CaseIterable { | ||
| case lamp | ||
| case road | ||
| case etc | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // | ||
| // RoutineCompletionEntity.swift | ||
| // Domain | ||
| // | ||
| // Created by 최정인 on 8/6/25. | ||
| // | ||
|
|
||
| public struct RoutineCompletionEntity { | ||
| public let performedDate: String | ||
| public let routineId: String | ||
| public let completeYn: Bool | ||
| public let historySeq: Int | ||
| public let routineType: String | ||
|
|
||
| public init( | ||
| performedDate: String, | ||
| routineId: String, | ||
| completeYn: Bool, | ||
| historySeq: Int, | ||
| routineType: String | ||
| ) { | ||
| self.performedDate = performedDate | ||
| self.routineId = routineId | ||
| self.completeYn = completeYn | ||
| self.historySeq = historySeq | ||
| self.routineType = routineType | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "camera_icon@1x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "1x" | ||
| }, | ||
| { | ||
| "filename" : "camera_icon@2x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "filename" : "camera_icon@3x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "location_icon@1x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "1x" | ||
| }, | ||
| { | ||
| "filename" : "location_icon@2x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "filename" : "location_icon@3x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "round_delete_icon@1x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "1x" | ||
| }, | ||
| { | ||
| "filename" : "round_delete_icon@2x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "filename" : "round_delete_icon@3x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // | ||
| // RequiredTitleLabel.swift | ||
| // Presentation | ||
| // | ||
| // Created by 이동현 on 11/8/25. | ||
| // | ||
|
|
||
| import SnapKit | ||
| import UIKit | ||
|
|
||
| final class RequiredTitleLabel: UIView { | ||
| private enum Layout { | ||
| static let asteriskLeadingSpacing: CGFloat = 3 | ||
| } | ||
|
|
||
| private let titleLabel = UILabel() | ||
| private let asteriskLabel = UILabel() | ||
|
|
||
| init(title: String) { | ||
| super.init(frame: .zero) | ||
| configureAttribute(title: title) | ||
| configureLayout() | ||
| } | ||
|
|
||
| required init?(coder: NSCoder) { | ||
| fatalError("init(coder:) has not been implemented") | ||
| } | ||
|
|
||
| private func configureAttribute(title: String) { | ||
| titleLabel.font = BitnagilFont.init( | ||
| style: .body2, | ||
| weight: .semiBold | ||
| ).font | ||
| titleLabel.textColor = BitnagilColor.gray10 | ||
| titleLabel.text = title | ||
|
|
||
| asteriskLabel.text = "*" | ||
| asteriskLabel.font = BitnagilFont.init( | ||
| style: .body1, | ||
| weight: .semiBold | ||
| ).font | ||
| asteriskLabel.textColor = BitnagilColor.error | ||
| } | ||
|
|
||
| private func configureLayout() { | ||
| addSubview(titleLabel) | ||
| addSubview(asteriskLabel) | ||
|
|
||
| titleLabel.snp.makeConstraints { make in | ||
| make.verticalEdges.leading.equalToSuperview() | ||
| } | ||
|
|
||
| asteriskLabel.snp.makeConstraints { make in | ||
| make.leading | ||
| .equalTo(titleLabel.snp.trailing) | ||
| .offset(Layout.asteriskLeadingSpacing) | ||
|
|
||
| make.top.equalTo(titleLabel) | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,17 +19,20 @@ final class SelectableItemTableView<T: SelectableItem & CaseIterable & Equatable | |
|
|
||
| private let itemTableView = UITableView() | ||
| private let items: [T] | ||
| private let markIsSelected: Bool | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요 부분이 리뷰 노트에 언급해주신 카메라 vs 앨범 선택일 시에는 선택 결과를 보여주지 않기 위해 추가된 값이죵 ?? 제가 금요일 회의를 참여하지 않아 정확한 상황을 파악하기 어려운 것 같아요 ㅠ.ㅠ
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그런 세세한 구현 내용은 다루지 않았지만,, 조이가 구현해두신 tableView + customBottomSheet를 사용하기 위해서는 SelectableItem을 채택한 프로토콜을 사용해야한다고 이해했습니다.!.! 사실 카메라 vs 앨범은 값을 선택해서 가지고 있는건 아니고, 일회성으로 선택하는 문제이긴 한데, 새로 tableView를 구현하기가 너무 귀찮았어요 ㅜㅜ 앞으로도 이런 화면이 추가적으로 나올 수도 있을 것 같은데, 새로 하나 구현해두는게 나을까요? |
||
| private var selectedItem: T? { | ||
| didSet { | ||
| if !markIsSelected && selectedItem == nil { return } | ||
| delegate?.selectableItemTableView(self, didSelectItem: selectedItem) | ||
| } | ||
| } | ||
|
|
||
| weak var delegate: SelectableItemTableViewDelegate? | ||
|
|
||
| init(items: [T], selectedItem: T? = nil) { | ||
| init(items: [T], selectedItem: T? = nil, markIsSelected: Bool = true) { | ||
| self.items = items.sorted(by: { $0.id < $1.id }) | ||
| self.selectedItem = selectedItem | ||
| self.markIsSelected = markIsSelected | ||
| super.init(nibName: nil, bundle: nil) | ||
| } | ||
|
|
||
|
|
@@ -93,6 +96,9 @@ final class SelectableItemTableView<T: SelectableItem & CaseIterable & Equatable | |
| } else { | ||
| self.selectedItem = selectedItem | ||
| } | ||
|
|
||
| if !markIsSelected { self.selectedItem = nil } | ||
|
|
||
| itemTableView.reloadData() | ||
| dismiss(animated: true) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -120,5 +120,9 @@ public struct PresentationDependencyAssembler: DependencyAssemblerProtocol { | |||||||||||||
|
|
||||||||||||||
| return WithdrawViewModel(authRepository: authRepository) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| DIContainer.shared.register(type: ReportViewModel.self) { container in | ||||||||||||||
| return ReportViewModel() | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+124
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용하지 않는 클로저 파라미터를 언더스코어로 변경하세요. ReportViewModel이 의존성을 필요로 하지 않아 다음 diff를 적용하세요: - DIContainer.shared.register(type: ReportViewModel.self) { container in
+ DIContainer.shared.register(type: ReportViewModel.self) { _ in
return ReportViewModel()
}📝 Committable suggestion
Suggested change
🧰 Tools🪛 SwiftLint (0.57.0)[Warning] 124-124: Unused parameter in a closure should be replaced with _ (unused_closure_parameter) 🤖 Prompt for AI Agents
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 먼가 추후에 서버 붙이면서 container 쓸 것 같어 ~ 돈와리 !! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @choijungp 알겠습니다! 향후 서버 연동 시 의존성을 추가할 계획이시라면 미리 🧠 Learnings used |
||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,123 @@ | ||||||||
| // | ||||||||
| // RoutineDeleteAlertView.swift | ||||||||
| // Presentation | ||||||||
| // | ||||||||
| // Created by 최정인 on 8/4/25. | ||||||||
| // | ||||||||
|
|
||||||||
| import SnapKit | ||||||||
| import UIKit | ||||||||
|
|
||||||||
| protocol RoutineDeleteAlertViewDelegate: AnyObject { | ||||||||
| func routineDeleteAlertViewDidTapDeleteAllRoutine(_ sender: RoutineDeleteAlertView) | ||||||||
| func routineDeleteAlertViewDidTapDeleteDailyRoutine(_ sender: RoutineDeleteAlertView) | ||||||||
| } | ||||||||
|
|
||||||||
| final class RoutineDeleteAlertView: UIView { | ||||||||
| private enum Layout { | ||||||||
| static let deleteLabelHeight: CGFloat = 48 | ||||||||
| static let deleteLabelTopSpacing: CGFloat = 23 | ||||||||
| static let buttonHorizontalMargin: CGFloat = 23 | ||||||||
| static let buttonHeight: CGFloat = 44 | ||||||||
| static let deleteDailyRoutineButtonTopSpacing: CGFloat = 22 | ||||||||
| static let deleteAllRoutineButtonTopSpacing: CGFloat = 10 | ||||||||
| } | ||||||||
|
|
||||||||
| private let contentView = UIView() | ||||||||
| private let deleteLabel = UILabel() | ||||||||
| private let deleteDailyRoutineButton = UIButton() | ||||||||
| private let deleteAllRoutineButton = UIButton() | ||||||||
| weak var delegate: RoutineDeleteAlertViewDelegate? | ||||||||
|
|
||||||||
| init() { | ||||||||
| super.init(frame: .zero) | ||||||||
| configureAttribute() | ||||||||
| configureLayout() | ||||||||
| } | ||||||||
|
|
||||||||
| required init?(coder: NSCoder) { | ||||||||
| fatalError("init(coder:) has not been implemented") | ||||||||
| } | ||||||||
|
|
||||||||
| private func configureAttribute() { | ||||||||
| contentView.backgroundColor = .white | ||||||||
| contentView.layer.masksToBounds = true | ||||||||
| contentView.layer.cornerRadius = 20 | ||||||||
|
|
||||||||
| deleteLabel.text = "해당 루틴은\n반복 루틴으로 설정되어 있어요" | ||||||||
| deleteLabel.numberOfLines = 2 | ||||||||
| deleteLabel.textAlignment = .center | ||||||||
| deleteLabel.font = BitnagilFont(style: .body1, weight: .semiBold).font | ||||||||
| deleteLabel.textColor = BitnagilColor.gray10 | ||||||||
|
|
||||||||
| var deleteDailyRoutineButtonConfiguration = UIButton.Configuration.filled() | ||||||||
| deleteDailyRoutineButtonConfiguration.baseBackgroundColor = .white | ||||||||
| deleteDailyRoutineButtonConfiguration.background.cornerRadius = 8 | ||||||||
| deleteDailyRoutineButtonConfiguration.attributedTitle = AttributedString( | ||||||||
| "당일만 삭제", | ||||||||
| attributes: .init([.font: BitnagilFont(style: .body2, weight: .medium).font])) | ||||||||
| deleteDailyRoutineButtonConfiguration.baseForegroundColor = BitnagilColor.navy500 | ||||||||
| deleteDailyRoutineButtonConfiguration.background.strokeColor = BitnagilColor.navy500 | ||||||||
| deleteDailyRoutineButtonConfiguration.background.strokeWidth = 1 | ||||||||
| deleteDailyRoutineButton.configuration = deleteDailyRoutineButtonConfiguration | ||||||||
| deleteDailyRoutineButton.addAction( | ||||||||
| UIAction { [weak self] _ in | ||||||||
| guard let self else { return } | ||||||||
| self.delegate?.routineDeleteAlertViewDidTapDeleteDailyRoutine(self) | ||||||||
| }, | ||||||||
| for: .touchUpInside) | ||||||||
|
|
||||||||
| var deleteAllRoutineButtonConfiguration = UIButton.Configuration.filled() | ||||||||
| deleteAllRoutineButtonConfiguration.baseBackgroundColor = .white | ||||||||
| deleteAllRoutineButtonConfiguration.background.cornerRadius = 8 | ||||||||
| deleteAllRoutineButtonConfiguration.attributedTitle = AttributedString( | ||||||||
| "전체 루틴 삭제", | ||||||||
| attributes: .init([.font: BitnagilFont(style: .body2, weight: .medium).font])) | ||||||||
| deleteAllRoutineButtonConfiguration.baseForegroundColor = BitnagilColor.navy500 | ||||||||
| deleteAllRoutineButtonConfiguration.background.strokeColor = BitnagilColor.navy500 | ||||||||
| deleteAllRoutineButtonConfiguration.background.strokeWidth = 1 | ||||||||
| deleteAllRoutineButton.configuration = deleteAllRoutineButtonConfiguration | ||||||||
| deleteAllRoutineButton.addAction( | ||||||||
| UIAction { [weak self] _ in | ||||||||
| guard let self else { return } | ||||||||
| self.delegate?.routineDeleteAlertViewDidTapDeleteAllRoutine(self) | ||||||||
| }, | ||||||||
| for: .touchUpInside) | ||||||||
| } | ||||||||
|
|
||||||||
| private func configureLayout() { | ||||||||
| addSubview(contentView) | ||||||||
|
|
||||||||
| contentView.snp.makeConstraints { make in | ||||||||
| make.edges.equalToSuperview() | ||||||||
| } | ||||||||
|
|
||||||||
| [deleteLabel, deleteDailyRoutineButton, deleteAllRoutineButton].forEach { | ||||||||
| contentView.addSubview($0) | ||||||||
| } | ||||||||
|
|
||||||||
| deleteLabel.snp.makeConstraints { make in | ||||||||
| make.height.equalTo(Layout.deleteLabelHeight) | ||||||||
| make.centerX.equalToSuperview() | ||||||||
| make.top.equalToSuperview().offset(Layout.deleteLabelTopSpacing) | ||||||||
| } | ||||||||
|
|
||||||||
| deleteDailyRoutineButton.snp.makeConstraints { make in | ||||||||
| make.top.equalTo(deleteLabel.snp.bottom).offset(Layout.deleteDailyRoutineButtonTopSpacing) | ||||||||
| make.leading.equalToSuperview().offset(Layout.buttonHorizontalMargin) | ||||||||
| make.trailing.equalToSuperview().inset(Layout.buttonHorizontalMargin) | ||||||||
| make.height.equalTo(Layout.buttonHeight) | ||||||||
| } | ||||||||
|
|
||||||||
| deleteAllRoutineButton.snp.makeConstraints { make in | ||||||||
| make.top.equalTo(deleteDailyRoutineButton.snp.bottom).offset(Layout.deleteAllRoutineButtonTopSpacing) | ||||||||
| make.leading.equalToSuperview().offset(Layout.buttonHorizontalMargin) | ||||||||
| make.trailing.equalToSuperview().inset(Layout.buttonHorizontalMargin) | ||||||||
| make.height.equalTo(Layout.buttonHeight) | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| private func scrollCollectionViewToEnd() { | ||||||||
|
|
||||||||
| } | ||||||||
|
Comment on lines
+120
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용되지 않는 메서드를 제거하세요. 이 메서드는 비어있고 호출되지 않는 데드 코드입니다. 코드베이스를 깔끔하게 유지하기 위해 제거해야 합니다. 다음 diff를 적용하여 제거하세요: - private func scrollCollectionViewToEnd() {
-
- }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||
| } | ||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // | ||
| // PhotoItem.swift | ||
| // Presentation | ||
| // | ||
| // Created by 이동현 on 11/9/25. | ||
| // | ||
|
|
||
| import Foundation | ||
|
|
||
| public struct PhotoItem: Hashable { | ||
| public let id: UUID | ||
| public let data: Data | ||
|
|
||
| public init(id: UUID = .init(), data: Data) { | ||
| self.id = id | ||
| self.data = data | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요 부분 뭔가 .. 리브랜딩 전 디자인이라 바뀔 수도 있을 것 같아요 !!!
아마 .. asterisk_icon으로 ?? (제 추측입니당 !!)
그래도
RequiredTitleLabel컴포넌트 빼서 하는거 넘 조은 것 같아요 !! 굿아 !!! 👍🏻There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
통일되면 ux면에서도 긍정적일 것 같습니다! 염두에 두고 있겠습니다~~!