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
4 changes: 2 additions & 2 deletions Projects/Domain/Sources/Entity/Enum/RepeatType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Created by 이동현 on 8/15/25.
//

public enum RepeatType {
public enum RepeatType: Equatable {
case daily
case weekly
case weekly(weeks: Set<Week>)
}
Comment on lines +8 to 11

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

케이스 시그니처 변경(.weekly → .weekly(weeks:))에 따른 사용처 점검 필요

연관값 추가로 기존 .weekly 패턴 매칭/생성이 컴파일 에러가 될 수 있습니다. 아래 스크립트로 레거시 사용을 찾아 점검해 주세요.


🏁 Script executed:

#!/bin/bash
# .weekly 연관값 없이 사용된 곳 탐지
rg -nP --type=swift -C2 '\.weekly(?!\s*\()'

Length of output: 2098


.weekly 케이스 연관값 처리 누락으로 인한 컴파일 에러 수정 필요

RepeatTypeweekly(weeks: Set<Week>)가 추가되면서, 기존의 .weekly 패턴 매칭/생성 코드는 모두 컴파일 에러가 발생합니다. 아래 위치들을 확인해 패턴 매칭 시 연관값 처리를 추가하거나 와일드카드로 무시하도록 수정하세요.

  • Projects/Presentation/Sources/RoutineCreation/ViewModel/RoutineCreationViewModel.swift
    • 186줄:
  • case .weekly:
  • case .weekly(let weeks):

• 187줄:
```diff

  • if case .weekly = current {
  • if case .weekly(_) = current {

• 198줄:
```diff

  • guard case .weekly = repeatTypeSubject.value else { return }
  • guard case .weekly(_) = repeatTypeSubject.value else { return }
    
    
  • Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineRepeatContentView.swift
    • 190줄:
  • case .weekly:
  • case .weekly(_):
    
    

필요하다면 let weeks로 바인딩하여 UI 업데이트에 활용할 수 있습니다.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fileprivate enum Layout {
static let titleLabelSmallHeight: CGFloat = 20
static let titleLabelTrailingSpacing: CGFloat = 5
static let placeHolderLabelHeight: CGFloat = 20
static let contentLabelHeight: CGFloat = 24
static let subTitleLabelHeight: CGFloat = 24
static let contentLabelStackViewSpacing: CGFloat = 1
static let infoImageSize: CGFloat = 16
static let asteriskImageSize: CGFloat = 12
Expand Down Expand Up @@ -130,7 +130,6 @@ final class RoutineCreationCardView<ContentView: UIView & RoutineCreationExpanda
make.top.equalToSuperview().offset(Layout.edgeSpacing)
make.leading.equalTo(titleImageView.snp.trailing).offset(Layout.titleImageTrailingSpacing)
make.trailing.equalToSuperview().offset(-Layout.edgeSpacing)
make.height.equalTo(Layout.titleLabelSmallHeight).priority(800)
labelStackViewBottomConstraint = make.bottom.equalToSuperview()
.offset(-Layout.edgeSpacing)
.priority(.medium)
Expand Down Expand Up @@ -165,7 +164,7 @@ final class RoutineCreationCardView<ContentView: UIView & RoutineCreationExpanda
}

divideLine.snp.makeConstraints { make in
make.top.equalTo(titleImageView.snp.bottom).offset(Layout.divideLineTopSpacing)
make.top.equalTo(labelStackView.snp.bottom).offset(Layout.divideLineTopSpacing)
make.horizontalEdges.equalToSuperview().inset(Layout.edgeSpacing)
make.height.equalTo(Layout.divideLineHeight)
}
Expand All @@ -175,12 +174,37 @@ final class RoutineCreationCardView<ContentView: UIView & RoutineCreationExpanda
make.horizontalEdges.equalToSuperview()
make.bottom.equalToSuperview().offset(-Layout.edgeSpacing)
}
}

func configure(subTitles: [String]) {
labelStackView.arrangedSubviews.dropFirst(2).forEach { subview in
labelStackView.removeArrangedSubview(subview)
subview.removeFromSuperview()
}

for text in subTitles {
let label = UILabel()
label.text = text
label.font = BitnagilFont(style: .body1, weight: .semiBold).font
label.textColor = BitnagilColor.gray10
label.numberOfLines = 1

labelStackView.addArrangedSubview(label)
label.snp.makeConstraints { make in
make.height.equalTo(Layout.subTitleLabelHeight)
}
}

titleLabel.snp.updateConstraints { make in
make.height.equalTo(subTitles.isEmpty ? Layout.titleLabelLargeHeight : Layout.titleLabelSmallHeight)
}

labelStackViewBottomConstraint?.isActive = true
updateHeaderVisibility()
setNeedsLayout()
}

func configure(dependencies: ContentView.Dependencies) {
contentView.configure(dependencies: dependencies)
func configure(dependencies: ContentView.Dependency) {
contentView.configure(dependency: dependencies)
}

@objc private func handleTap(_ gestureRecognizer: UIGestureRecognizer) {
Expand All @@ -192,19 +216,16 @@ final class RoutineCreationCardView<ContentView: UIView & RoutineCreationExpanda
}

private func configureContents(contents: [String]) {
guard !contents.isEmpty else {
placeHolderLabel.isHidden = false
titleLabel.snp.updateConstraints { make in
make.height.equalTo(Layout.titleLabelLargeHeight)
}
return
defer {
updateHeaderVisibility()
setNeedsLayout()
}

placeHolderLabel.isHidden = true
guard !contents.isEmpty else { return }

labelStackView
.arrangedSubviews
.dropFirst(2) // title, placeholder 제외한 label들 삭제
.dropFirst(2)
.forEach { $0.removeFromSuperview() }

contents.forEach {
Expand All @@ -215,13 +236,9 @@ final class RoutineCreationCardView<ContentView: UIView & RoutineCreationExpanda

labelStackView.addArrangedSubview(label)
label.snp.makeConstraints { make in
make.height.equalTo(Layout.contentLabelHeight)
make.height.equalTo(Layout.subTitleLabelHeight)
}
}

titleLabel.snp.updateConstraints { make in
make.height.equalTo(Layout.titleLabelSmallHeight)
}
}

private func toggleExpand() {
Expand All @@ -230,20 +247,45 @@ final class RoutineCreationCardView<ContentView: UIView & RoutineCreationExpanda

if nextExpandedState {
labelStackViewBottomConstraint?.isActive = false
placeHolderLabel.isHidden = true
contentView.isHidden = false
divideLine.isHidden = false
chevronImageView.image = BitnagilIcon.chevronIcon(direction: .up)
} else {
labelStackViewBottomConstraint?.isActive = true
placeHolderLabel.isHidden = false
contentView.isHidden = true
divideLine.isHidden = true
chevronImageView.image = BitnagilIcon.chevronIcon(direction: .down)
}

contentView.setExpanded(expanded: nextExpandedState)
updateHeaderVisibility()

self.layoutIfNeeded()
}

private func updateHeaderVisibility() {
let arranged = labelStackView.arrangedSubviews
let subtitles = arranged.dropFirst(2)
let hasSubtitles = !subtitles.isEmpty
let isExpanded = !contentView.isHidden
let titleHeight: CGFloat = {
if isExpanded { return Layout.titleLabelLargeHeight }
return hasSubtitles ? Layout.titleLabelSmallHeight : Layout.titleLabelLargeHeight
}()

placeHolderLabel.isHidden = isExpanded || hasSubtitles
subtitles.forEach { $0.isHidden = isExpanded }

titleLabel.snp.updateConstraints { make in
make.height.equalTo(titleHeight)
}

if titleHeight == Layout.titleLabelLargeHeight {
titleLabel.font = isExpanded ? BitnagilFont.init(style: .body2, weight: .semiBold).font : BitnagilFont.init(style: .body1, weight: .semiBold).font
titleLabel.textColor = BitnagilColor.gray10
} else {
titleLabel.font = BitnagilFont.init(style: .body2, weight: .medium).font
titleLabel.textColor = BitnagilColor.gray50
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ final class RoutineNameContentView: UIView, RoutineCreationExpandable {
static let textFieldHeight: CGFloat = 20
static let divideLineHeight: CGFloat = 1.3
static let textFieldTopSpacing: CGFloat = 23
static let checkButtonViewTopSpacing: CGFloat = 24
static let checkButtonViewSize: CGFloat = 18
static let checkButtonImageViewTopSpacing: CGFloat = 24
static let checkButtonImageViewSize: CGFloat = 18
static let checkButtonLabelHeight: CGFloat = 20
static let checkButtonLabelTrailingSpacing: CGFloat = 6
static let foldedHeight: CGFloat = 0
Expand All @@ -29,7 +29,7 @@ final class RoutineNameContentView: UIView, RoutineCreationExpandable {
case deleteAllSubroutines
}

struct Dependencies {
struct Dependency {
let subRoutines: [String]
}

Expand All @@ -43,7 +43,7 @@ final class RoutineNameContentView: UIView, RoutineCreationExpandable {
private let divideLine2 = UIImageView()
private let divideLine3 = UIImageView()
private let checkButtonLabel = UILabel()
private let checkButtonView = UIView()
private let checkButtonImageView = UIImageView()
private let checkButton = UIButton()
var heightConstraint: Constraint?
var action: ((Action) -> Void)?
Expand All @@ -70,10 +70,10 @@ final class RoutineNameContentView: UIView, RoutineCreationExpandable {
self.subviews.forEach { $0.isHidden = !expanded }
}

func configure(dependencies: Dependencies) {
guard !dependencies.subRoutines.isEmpty else { return }
func configure(dependency: Dependency) {
guard !dependency.subRoutines.isEmpty else { return }

Comment on lines +73 to 75

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

빈 배열 입력 시에도 UI가 정상 초기화되도록 가드 제거 및 나머지 필드 초기화

빈 배열이면 return 되어 체크 아이콘/텍스트필드 초기화가 누락될 수 있습니다. 가드를 제거하고, 전달된 subRoutines 수보다 많은 텍스트필드는 비워 주세요.

-    func configure(dependency: Dependency) {
-        guard !dependency.subRoutines.isEmpty else { return }
+    func configure(dependency: Dependency) {
 
         let subRoutines = dependency.subRoutines
         let subRoutineTextFields = [
             subRoutineTextField1,
             subRoutineTextField2,
             subRoutineTextField3]
         let minCount = min(subRoutines.count, subRoutineTextFields.count)
 
-        zip(subRoutines, subRoutineTextFields).prefix(minCount).forEach {
-            $1.text = $0
-        }
+        zip(subRoutines, subRoutineTextFields).prefix(minCount).forEach { $1.text = $0 }
+        // 남은 필드는 초기화
+        subRoutineTextFields.dropFirst(minCount).forEach { $0.text = "" }

Also applies to: 83-86

🤖 Prompt for AI Agents
In
Projects/Presentation/Sources/RoutineCreation/View/Component/RoutineNameContentView.swift
around lines 73–75 (and similarly 83–86), remove the early guard that returns on
empty dependency.subRoutines so UI always initializes; always set the check-icon
state and configure each text field up to dependency.subRoutines.count with the
provided subRoutine values, and explicitly clear any remaining text fields and
reset their check-icons when dependency.subRoutines.count is less than the total
fields so no stale text or checked state remains.

let subRoutines = dependencies.subRoutines
let subRoutines = dependency.subRoutines
let subRoutineTextFields = [
subRoutineTextField1,
subRoutineTextField2,
Expand All @@ -83,6 +83,12 @@ final class RoutineNameContentView: UIView, RoutineCreationExpandable {
zip(subRoutines, subRoutineTextFields).prefix(minCount).forEach {
$1.text = $0
}

if dependency.subRoutines.filter({ !$0.isEmpty }).isEmpty {
checkButtonImageView.image = BitnagilIcon.checkedIcon
} else {
checkButtonImageView.image = BitnagilIcon.uncheckedIcon
}
}

private func configureAttribute() {
Expand All @@ -100,15 +106,25 @@ final class RoutineNameContentView: UIView, RoutineCreationExpandable {
subRoutineTextField2,
subRoutineTextField3]

zip(textFields, placeholders).forEach { textField, placeholder in
let attributes: [NSAttributedString.Key: Any] = [
.font: BitnagilFont(style: .body2, weight: .medium).font,
.foregroundColor: BitnagilColor.gray90 ?? .systemGray]

textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: attributes)
textField.font = BitnagilFont(style: .body2, weight: .medium).font
textField.textColor = BitnagilColor.gray30
}
zip(textFields, placeholders)
.enumerated()
.forEach {
let index = $0
let (textField, placeholder) = $1

let attributes: [NSAttributedString.Key: Any] = [
.font: BitnagilFont(style: .body2, weight: .medium).font,
.foregroundColor: BitnagilColor.gray90 ?? .systemGray
]

textField.delegate = self
textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: attributes)
textField.font = BitnagilFont(style: .body2, weight: .medium).font
textField.textColor = BitnagilColor.gray30
textField.tag = index
textField.returnKeyType = .done
textField.addTarget(self, action: #selector(textFieldEditingChanged(_:)), for: .editingChanged)
}

divideLine1.image = BitnagilIcon.divideLineIcon
divideLine2.image = BitnagilIcon.divideLineIcon
Expand All @@ -117,10 +133,16 @@ final class RoutineNameContentView: UIView, RoutineCreationExpandable {
checkButtonLabel.text = "세부루틴 설정 안함"
checkButtonLabel.font = BitnagilFont.init(style: .body2, weight: .medium).font

checkButtonView.layer.cornerRadius = 4
checkButtonView.layer.borderWidth = 1
checkButtonView.backgroundColor = .white
checkButtonView.layer.borderColor = BitnagilColor.gray95?.cgColor
checkButtonImageView.layer.cornerRadius = 4
checkButtonImageView.layer.masksToBounds = true
checkButtonImageView.layer.borderWidth = 1
checkButtonImageView.backgroundColor = .white
checkButtonImageView.layer.borderColor = BitnagilColor.gray95?.cgColor
checkButton.addAction(
UIAction { [weak self] _ in
self?.action?(.deleteAllSubroutines)
},
for: .touchUpInside)
}

private func configureLayout() {
Expand All @@ -134,7 +156,7 @@ final class RoutineNameContentView: UIView, RoutineCreationExpandable {
addSubview(divideLine2)
addSubview(divideLine3)
addSubview(checkButtonLabel)
addSubview(checkButtonView)
addSubview(checkButtonImageView)
addSubview(checkButton)

self.snp.makeConstraints { make in
Expand Down Expand Up @@ -198,23 +220,33 @@ final class RoutineNameContentView: UIView, RoutineCreationExpandable {
make.height.equalTo(Layout.divideLineHeight).priority(999)
}

checkButtonView.snp.makeConstraints { make in
make.top.equalTo(divideLine3.snp.bottom).offset(Layout.checkButtonViewTopSpacing).priority(999)
checkButtonImageView.snp.makeConstraints { make in
make.top.equalTo(divideLine3.snp.bottom).offset(Layout.checkButtonImageViewTopSpacing).priority(999)
make.bottom.equalToSuperview().offset(-Layout.edgeSpacing).priority(999)
make.trailing.equalToSuperview().offset(-Layout.edgeSpacing)
make.size.equalTo(Layout.checkButtonViewSize).priority(999)
make.size.equalTo(Layout.checkButtonImageViewSize).priority(999)
}

checkButtonLabel.snp.makeConstraints { make in
make.trailing.equalTo(checkButtonView.snp.leading).offset(-Layout.checkButtonLabelTrailingSpacing)
make.centerY.equalTo(checkButtonView)
make.trailing.equalTo(checkButtonImageView.snp.leading).offset(-Layout.checkButtonLabelTrailingSpacing)
make.centerY.equalTo(checkButtonImageView)
}

checkButton.snp.makeConstraints { make in
make.verticalEdges.equalTo(checkButtonView)
make.verticalEdges.equalTo(checkButtonImageView)
make.leading.equalTo(checkButtonLabel.snp.leading)
make.trailing.equalTo(checkButtonView.snp.trailing)
make.trailing.equalTo(checkButtonImageView.snp.trailing)
}
}


@objc private func textFieldEditingChanged(_ sender: UITextField) {
action?(.subroutineChanged(index: sender.tag, text: sender.text ?? ""))
}
}

extension RoutineNameContentView: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ final class RoutinePeriodContentView: UIView, RoutineCreationExpandable {
case endDateSetTapped
}

struct Dependencies {
let start: Date
let end: Date
struct Dependency {
let dates: (start: Date, end: Date)
}

private let startLabel = UILabel()
Expand Down Expand Up @@ -65,9 +64,15 @@ final class RoutinePeriodContentView: UIView, RoutineCreationExpandable {
self.subviews.forEach { $0.isHidden = !expanded }
}

func configure(dependencies: Dependencies) {
let startString = dependencies.start.convertToString(dateType: .yearMonthDate)
let endString = dependencies.end.convertToString(dateType: .yearMonthDate)
func configure(dependency: Dependency) {
let startString = dependency
.dates
.start
.convertToString(dateType: .yearMonthDate)
let endString = dependency
.dates
.end
.convertToString(dateType: .yearMonthDate)

startButton.setTitle(startString, for: .normal)
endButton.setTitle(endString, for: .normal)
Expand Down
Loading
Loading