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
2 changes: 2 additions & 0 deletions Projects/Presentation/Sources/Common/Extension/Date+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extension Date {
case dayOfWeek
case date
case amPmTime
case amPmTimeShort

var formatString: String {
switch self {
Expand All @@ -30,6 +31,7 @@ extension Date {
case .dayOfWeek: "E"
case .date: "d"
case .amPmTime: "a HH:mm"
case .amPmTimeShort: "a h:mm"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// DatePickerViewController.swift
// Presentation
//
// Created by 이동현 on 7/27/25.
//

import SnapKit
import UIKit

protocol DatePickerViewDelegate: AnyObject {
func datePickerView(_ pickerView: DatePickerView, didSelectTime time: Date)
}

final class DatePickerView: UIViewController {
private enum Layout {
static let datePickerHeight: CGFloat = 195
static let horizontalSpacing: CGFloat = 20
static let registerButtonHeight: CGFloat = 54
static let registerButtonVerticalSpacing: CGFloat = 14
}

private let datePicker = UIDatePicker()
private let registerButton = UIButton()
weak var delegate: DatePickerViewDelegate?

override func viewDidLoad() {
super.viewDidLoad()
configureAttribute()
configureLayout()
}

private func configureAttribute() {
datePicker.preferredDatePickerStyle = .wheels
datePicker.datePickerMode = .time
datePicker.locale = Locale(identifier: "en_US")
datePicker.backgroundColor = .white
datePicker.tintColor = .black

registerButton.layer.cornerRadius = 12
registerButton.layer.masksToBounds = true
registerButton.backgroundColor = BitnagilColor.navy500
registerButton.titleLabel?.font = BitnagilFont.init(style: .body1, weight: .semiBold).font
registerButton.setTitle("등록하기", for: .normal)
registerButton.setTitleColor(.white, for: .normal)
registerButton.addAction(
UIAction { [weak self] _ in
guard let self else { return }
self.delegate?.datePickerView(self, didSelectTime: datePicker.date)
dismiss(animated: true)
},
for: .touchUpInside)
}

private func configureLayout() {
let safeArea = view.safeAreaLayoutGuide
view.addSubview(datePicker)
view.addSubview(registerButton)

datePicker.snp.makeConstraints { make in
make.top.horizontalEdges.equalToSuperview()
make.height.equalTo(Layout.datePickerHeight)
}

registerButton.snp.makeConstraints { make in
make.horizontalEdges.equalToSuperview().inset(Layout.horizontalSpacing)
make.top.equalTo(datePicker.snp.bottom).offset(Layout.registerButtonVerticalSpacing)
make.bottom.equalTo(safeArea.snp.bottom).offset(-Layout.registerButtonVerticalSpacing)
make.height.equalTo(Layout.registerButtonHeight)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ final class RoutineCreationView: BaseViewController<RoutineCreationViewModel> {
static let registerButtonTopSpacing: CGFloat = 54
static let registerButtonHeight: CGFloat = 54
static let registerButtonBottomSpacing: CGFloat = 14
static let datePickerBottomSheetHeight: CGFloat = 347
}

private let scrollView = UIScrollView()
Expand All @@ -70,6 +71,7 @@ final class RoutineCreationView: BaseViewController<RoutineCreationViewModel> {
private let startTimeAsterisk = UIImageView()
private let timePickerButton = RoutineTimePickerButton()
private let allDayButton = UIButton()
private let allDayLabelButton = UIButton()
private let allDayLabel = UILabel()
private let weekdaysStackView = UIStackView()

Expand Down Expand Up @@ -201,6 +203,7 @@ final class RoutineCreationView: BaseViewController<RoutineCreationViewModel> {
contentView.addSubview(startTimeTitleLabel)
contentView.addSubview(startTimeAsterisk)
contentView.addSubview(allDayButton)
contentView.addSubview(allDayLabelButton)
contentView.addSubview(allDayLabel)
contentView.addSubview(timePickerButton)

Expand Down Expand Up @@ -341,6 +344,10 @@ final class RoutineCreationView: BaseViewController<RoutineCreationViewModel> {
make.centerY.equalTo(startTimeTitleLabel)
}

allDayLabelButton.snp.makeConstraints { make in
make.edges.equalTo(allDayLabel)
}

timePickerButton.snp.makeConstraints { make in
make.top.equalTo(startTimeTitleLabel.snp.bottom).offset(Layout.titleLabelBottomSpacing)
make.horizontalEdges.equalToSuperview().inset(Layout.horizontalInset)
Expand Down Expand Up @@ -436,10 +443,9 @@ final class RoutineCreationView: BaseViewController<RoutineCreationViewModel> {

viewModel.output.executionTimePublisher
.receive(on: DispatchQueue.main)
.sink { [weak self] executionTime in
self?.timePickerButton.configure(title: executionTime.description)

let allDayButtonImage = executionTime == .allDay
.sink { [weak self] executionType in
self?.timePickerButton.configure(title: executionType.description)
let allDayButtonImage = executionType == "하루종일"
? BitnagilIcon.checkedIcon
: BitnagilIcon.uncheckedIcon
self?.allDayButton.setImage(allDayButtonImage, for: .normal)
Expand Down Expand Up @@ -498,9 +504,19 @@ final class RoutineCreationView: BaseViewController<RoutineCreationViewModel> {
},
for: .touchUpInside)

allDayButton.addAction(
[allDayButton, allDayLabelButton].forEach {
$0.addAction(
UIAction { [weak self] _ in
self?.viewModel.action(input: .configureExecution(type: .allDay))
},
for: .touchUpInside)
}

timePickerButton.addAction(
UIAction { [weak self] _ in
self?.viewModel.action(input: .configureExecution(type: .allDay))
let datePickerView = DatePickerView()
datePickerView.delegate = self
self?.presentCustomBottomSheet(contentViewController: datePickerView, maxHeight: Layout.datePickerBottomSheetHeight)
},
for: .touchUpInside)
}
Expand Down Expand Up @@ -607,3 +623,9 @@ extension RoutineCreationView: RoutineCreationInputViewDelegate {
viewModel.action(input: .configureSubRoutine(name: text, index: index))
}
}

extension RoutineCreationView: DatePickerViewDelegate {
func datePickerView(_ pickerView: DatePickerView, didSelectTime time: Date) {
viewModel.action(input: .configureExecution(type: .time(startAt: time)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by 이동현 on 7/20/25.
//
import Combine
import Foundation

final class RoutineCreationViewModel: ViewModel {
enum RepeatType {
Expand All @@ -20,14 +21,14 @@ final class RoutineCreationViewModel: ViewModel {
}

enum ExecutionType: Comparable {
case time(startAt: String)
case time(startAt: Date)
case allDay
case none

var description: String {
switch self {
case .time(let time):
return time
return time.convertToString(dateType: .amPmTimeShort)
case .allDay:
return "하루종일"
case .none:
Expand All @@ -53,7 +54,7 @@ final class RoutineCreationViewModel: ViewModel {
let subRoutinesPublisher: AnyPublisher<[String], Never>
let repeatTypePublisher: AnyPublisher<RepeatType?, Never>
let weekDayPublisher: AnyPublisher<Set<Week>, Never>
let executionTimePublisher: AnyPublisher<ExecutionType, Never>
let executionTimePublisher: AnyPublisher<String, Never>
let isRoutineValid: AnyPublisher<Bool, Never>
}

Expand All @@ -71,7 +72,9 @@ final class RoutineCreationViewModel: ViewModel {
subRoutinesPublisher: subRoutinesSubject.eraseToAnyPublisher(),
repeatTypePublisher: repeatTypeSubject.eraseToAnyPublisher(),
weekDayPublisher: weekDaySubject.eraseToAnyPublisher(),
executionTimePublisher: executionTimeSubject.eraseToAnyPublisher(),
executionTimePublisher: executionTimeSubject
.map{ $0.description }
.eraseToAnyPublisher(),
isRoutineValid: checkRoutinePublisher.eraseToAnyPublisher())
}

Expand All @@ -90,9 +93,9 @@ final class RoutineCreationViewModel: ViewModel {
case .toggleRepeatDay(let weekDay):
configureWeekDay(weekDay: weekDay)
case .toggleRepeatAllDay:
configurExecutionTime(time: .allDay)
configureExecutionTime(type: .allDay)
case .configureExecution(let startTime):
configurExecutionTime(time: startTime)
configureExecutionTime(type: startTime)
case .registerRoutine:
registerRoutine()
}
Expand Down Expand Up @@ -159,16 +162,16 @@ final class RoutineCreationViewModel: ViewModel {
weekDaySubject.send(weekDays)
}

private func configurExecutionTime(time: ExecutionType) {
private func configureExecutionTime(type: ExecutionType) {
if
time == .allDay,
type == .allDay,
executionTimeSubject.value == .allDay
{
executionTimeSubject.send(.none)
return
}

executionTimeSubject.send(time)
executionTimeSubject.send(type)
}

private func updateIsRoutineValid() {
Expand Down
Loading