Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Projects/Domain/Sources/Entity/Enum/ReportType.swift
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
}
28 changes: 28 additions & 0 deletions Projects/Domain/Sources/Entity/RoutineCompletionEntity.swift
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
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
Comment on lines +37 to +42

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 부분 뭔가 .. 리브랜딩 전 디자인이라 바뀔 수도 있을 것 같아요 !!!
아마 .. asterisk_icon으로 ?? (제 추측입니당 !!)

그래도 RequiredTitleLabel 컴포넌트 빼서 하는거 넘 조은 것 같아요 !! 굿아 !!! 👍🏻

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

통일되면 ux면에서도 긍정적일 것 같습니다! 염두에 두고 있겠습니다~~!

}

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
Expand Up @@ -19,17 +19,20 @@ final class SelectableItemTableView<T: SelectableItem & CaseIterable & Equatable

private let itemTableView = UITableView()
private let items: [T]
private let markIsSelected: Bool

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 부분이 리뷰 노트에 언급해주신 카메라 vs 앨범 선택일 시에는 선택 결과를 보여주지 않기 위해 추가된 값이죵 ??
단순 궁금한게 있는데 카메라 vs 앨범 선택도 SelectableItemTable을 쓰기로 했나요 ??!!!

제가 금요일 회의를 참여하지 않아 정확한 상황을 파악하기 어려운 것 같아요 ㅠ.ㅠ
죄송함니다 ....... ..

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The 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)
}

Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ enum BitnagilIcon {
static let settingIcon = UIImage(named: "setting_icon", in: bundle, with: nil)
static let exclamationFilledIcon = UIImage(named: "exclamation_filled_icon", in: bundle, with: nil)

// MARK: - Report
static let roundDeleteIcon = UIImage(named: "round_delete_icon", in: bundle, with: nil)
static let cameraIcon = UIImage(named: "camera_icon", in: bundle, with: nil)
static let locationIcon = UIImage(named: "location_icon", in: bundle, with: nil)

// MARK: - Routine Creation Icons
static let asteriskIcon = UIImage(named: "asterisk_icon", in: bundle, with: nil)
static let deleteIcon = UIImage(named: "delete_icon", in: bundle, with: nil)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

@coderabbitai coderabbitai Bot Nov 9, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

사용하지 않는 클로저 파라미터를 언더스코어로 변경하세요.

ReportViewModel이 의존성을 필요로 하지 않아 container 파라미터가 사용되지 않습니다. SwiftLint 경고를 해결하기 위해 파라미터명을 _로 변경해주세요.

다음 diff를 적용하세요:

-        DIContainer.shared.register(type: ReportViewModel.self) { container in
+        DIContainer.shared.register(type: ReportViewModel.self) { _ in
             return ReportViewModel()
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DIContainer.shared.register(type: ReportViewModel.self) { container in
return ReportViewModel()
}
DIContainer.shared.register(type: ReportViewModel.self) { _ in
return ReportViewModel()
}
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 124-124: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🤖 Prompt for AI Agents
In Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift
around lines 124 to 126, the closure passed to DIContainer.shared.register
declares an unused parameter named `container`; rename this parameter to `_` so
the closure signature becomes `{ _ in ... }` (or simply `{ _ in return
ReportViewModel() }`) to silence the SwiftLint unused-parameter warning while
keeping behavior unchanged.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

먼가 추후에 서버 붙이면서 container 쓸 것 같어 ~ 돈와리 !!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@choijungp 알겠습니다! 향후 서버 연동 시 의존성을 추가할 계획이시라면 미리 container 파라미터를 유지하는 것도 좋은 선택입니다. SwiftLint 경고는 남아있겠지만, 나중에 코드를 수정하는 수고를 덜 수 있겠네요. 👍


🧠 Learnings used
Learnt from: choijungp
Repo: YAPP-Github/Bitnagil-iOS PR: 30
File: Projects/DataSource/Sources/NetworkService/Plugin/RefreshTokenPlugin.swift:42-43
Timestamp: 2025-07-30T03:56:18.617Z
Learning: choijungp는 현재 테스트 단계에서 RefreshTokenPlugin의 토큰 갱신 로직 디버깅을 위해 액세스 토큰과 리프레시 토큰의 실제 값을 로그에 기록하는 것을 선호함.

}
}
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

사용되지 않는 메서드를 제거하세요.

이 메서드는 비어있고 호출되지 않는 데드 코드입니다. 코드베이스를 깔끔하게 유지하기 위해 제거해야 합니다.

다음 diff를 적용하여 제거하세요:

-    private func scrollCollectionViewToEnd() {
-
-    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private func scrollCollectionViewToEnd() {
}
🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/Home/View/Component/RoutineDeleteAlertView.swift
around lines 120 to 122, remove the unused empty method
scrollCollectionViewToEnd() (delete its entire declaration and surrounding blank
lines); ensure there are no remaining references to this method elsewhere
(search and remove calls if any) and run project build/tests after deletion to
confirm no compile errors.

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final class HomeViewModel: ViewModel {
let updateVersionPublisher: AnyPublisher<URL?, Never>
}

private(set) var output: Output
let output: Output
private var routines: [String: [Routine]] = [:]
private var routinesCompleted: [String: Bool] = [:]
private let nicknameSubject = CurrentValueSubject<String, Never>("")
Expand Down
18 changes: 18 additions & 0 deletions Projects/Presentation/Sources/Report/Model/PhotoItem.swift
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
}
}
Loading