Skip to content

Commit

Permalink
feat: Create DodamDatePicker
Browse files Browse the repository at this point in the history
  • Loading branch information
hhhello0507 committed Jul 27, 2024
1 parent 22d0a88 commit 3740991
Show file tree
Hide file tree
Showing 8 changed files with 277 additions and 10 deletions.
225 changes: 225 additions & 0 deletions Source/DDS/Component/Modal/DatePicker/DatePickerPresenter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import SwiftUI

public struct DodamDatePickerPresenter<C: View>: ModalViewProtocol {
@StateObject private var provider: DatePickerProvider
@State private var size: CGSize = .zero
@State private var monthDate: Date = .now
private let calendar = {
var calendar = Calendar.current
calendar.locale = Locale(identifier: "ko-KR")
return calendar
}()
private let weekdays = ["", "", "", "", "", "", ""]
let content: () -> C

init(
provider: DatePickerProvider,
@ViewBuilder content: @escaping () -> C
) {
self._provider = .init(wrappedValue: provider)
self.content = content
}

func dismiss() {
provider.isPresent = false
}

private var range: Int? {
calendar.range(of: .day, in: .month, for: monthDate)?.count
}

private var weeks: [[Date?]] {
// 해당 월의 첫째 날
var components = calendar.dateComponents([.year, .month], from: monthDate)
components.day = 1
let firstDayOfMonth = calendar.date(from: components)!

// 첫째 날의 요일 (일요일 = 1, 월요일 = 2, ..., 토요일 = 7)
let firstWeekday = calendar.component(.weekday, from: firstDayOfMonth)

// 날짜 배열 생성
var days: [Date?] = Array(repeating: nil, count: firstWeekday - 1)
days += Array(1...(range ?? 0)).compactMap {
components.day = $0
return calendar.date(from: components)
}
days += Array(repeating: nil, count: (7 - days.count % 7) % 7)

// 주 단위로 배열을 나눔
return stride(from: 0, to: days.count, by: 7).map {
Array(days[$0..<$0 + 7])
}
}

private func isEnabled(_ date: Date, between startDate: Date, and endDate: Date) -> Bool {
let startComponents = calendar.dateComponents([.year, .month, .day], from: startDate)
let endComponents = calendar.dateComponents([.year, .month, .day], from: endDate)
let dateComponents = calendar.dateComponents([.year, .month, .day], from: date)

if let start = calendar.date(from: startComponents),
let end = calendar.date(from: endComponents),
let target = calendar.date(from: dateComponents) {
return (start...end).contains(target)
}

return false
}

public var body: some View {
BaseModal(
isPresent: $provider.isPresent,
content: content
) {
VStack(spacing: 16) {
header
calendarView
HStack {
Spacer()
DodamTextButton.large(title: "선택", color: DodamColor.Primary.normal) {
provider.action()
dismiss()
}
}
}
.padding(24)
.frame(width: 328)
.clipShape(.extraLarge)
}
.animation(.spring, value: monthDate)
}

@ViewBuilder
private var header: some View {
VStack(spacing: 4) {
Text(provider.title)
.heading2(.bold)
.foreground(DodamColor.Label.strong)
.frame(maxWidth: .infinity, alignment: .leading)
HStack(spacing: 8) {
Text(
String(calendar.dateComponents([.year], from: monthDate).year ?? 0)
+ ""
+ String(calendar.dateComponents([.month], from: monthDate).month ?? 0)
+ "")
.body1(.medium)
.foreground(DodamColor.Label.strong)
Spacer()
Button {
if let date = calendar.date(byAdding: .month, value: -1, to: monthDate) {
self.monthDate = date
}
} label: {
Image(icon: .chevronLeft)
.resizable()
.foreground(DodamColor.Primary.normal)
.frame(width: 20, height: 20)
.padding(8)
}
Button {
if let date = calendar.date(byAdding: .month, value: 1, to: monthDate) {
self.monthDate = date
}
} label: {
Image(icon: .chevronRight)
.resizable()
.foreground(DodamColor.Primary.normal)
.frame(width: 20, height: 20)
.padding(8)
}
}
}
.animation(.none, value: monthDate)
}

@ViewBuilder
private var calendarView: some View {
VStack(spacing: 0) {
// header
HStack(spacing: 0) {
ForEach(weekdays, id: \.self) { week in
Text(week)
.label(.regular)
.foreground(DodamColor.Label.alternative)
.frame(maxWidth: .infinity)
}
}
// days
ForEach(weeks, id: \.self) { week in
HStack(spacing: 0) {
ForEach(week, id: \.self) { day in
let enabled = isEnabled(
day ?? .now,
between: provider.startDate ?? .now,
and: provider.endDate ?? .now
)
let selected = day == provider.date
Button {
provider.date = day ?? .now
} label: {
Text(day == nil ? "" : "\(calendar.component(.day, from: day!))")
.headline(.medium)
.frame(maxWidth: .infinity)
.padding(.vertical, 4)
.opacity({
guard day != nil else {
return 0
}
return enabled ? 1 : 0.5
}())
.foreground(
selected
? DodamColor.Static.white
: DodamColor.Label.alternative
)
.background {
if selected {
Rectangle()
.dodamFill(DodamColor.Primary.normal)
.frame(width: 38, height: 38)
.clipShape(.small)
}
}
}
.disabled(!enabled)
}
}
}
}
.animation(.none, value: monthDate)
}
}

private struct DatePickerPreview: View {
@StateObject private var provider = DatePickerProvider()
@State var hour = 0
@State var minute = 0
var body: some View {
DodamDatePickerPresenter(provider: provider) {
VStack {
Button("Show") {
provider.present(
"외출 일시",
startDate: .now,
endDate: {
var d = Date.now
d = Calendar.current.date(byAdding: .weekOfYear, value: 1, to: d)!
return d
}()
) {
print("Hello")
}
}
}
}
.registerPretendard()
}
}

#Preview {
DatePickerPreview()
}

#Preview {
DatePickerPreview()
.preferredColorScheme(.dark)
}
32 changes: 32 additions & 0 deletions Source/DDS/Component/Modal/DatePicker/DatePickerProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// File.swift
//
//
// Created by hhhello0507 on 7/27/24.
//

import Foundation

public final class DatePickerProvider: ModalProvider {
@Published var isPresent = false
@Published public var date: Date = .now

@Published var title: String = ""
@Published var startDate: Date?
@Published var endDate: Date?
@Published var action: () -> Void = {}

public func present(
_ title: String,
startDate: Date?,
endDate: Date?,
action: @escaping () -> Void
) {
self.date = .now
self.title = title
self.startDate = startDate
self.endDate = endDate
self.action = action
self.isPresent = true
}
}
1 change: 0 additions & 1 deletion Source/DDS/Component/Modal/Dialog/DialogPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Combine

public struct DodamDialogPresenter<C: View>: ModalViewProtocol {

public typealias P = DialogProvider
@StateObject private var provider: DialogProvider

let content: () -> C
Expand Down
2 changes: 1 addition & 1 deletion Source/DDS/Component/Modal/Dialog/DialogProvider.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Foundation

public final class DialogProvider: ObservableObject, ModalProvider {
@Published var isPresent = false

@Published public var isPresent = false
@Published var title: String = ""
@Published var message: String?
@Published var secondaryButton: DialogButton?
Expand Down
16 changes: 14 additions & 2 deletions Source/DDS/Component/Modal/DodamModalPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,32 @@ import SwiftUI
public struct DodamModalProvider<C: View>: View {

private let dialogProvider: DialogProvider
private let datePickerProvider: DatePickerProvider
private let timePickerProvider: TimePickerProvider
private let content: () -> C

public init(
dialogProvider: DialogProvider,
datePickerProvider: DatePickerProvider,
timePickerProvider: TimePickerProvider,
@ViewBuilder content: @escaping () -> C
) {
self.dialogProvider = dialogProvider
self.datePickerProvider = datePickerProvider
self.timePickerProvider = timePickerProvider
self.content = content
}

public var body: some View {
DodamDialogPresenter(provider: dialogProvider) {
content()
.environmentObject(dialogProvider)
DodamDatePickerPresenter(provider: datePickerProvider) {
DodamTimePickerPresenter(provider: timePickerProvider) {
content()
.environmentObject(dialogProvider)
.environmentObject(datePickerProvider)
.environmentObject(timePickerProvider)
}
}
}
}
}
1 change: 0 additions & 1 deletion Source/DDS/Component/Modal/ModalViewProtocol.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import SwiftUI

protocol ModalViewProtocol: View {
associatedtype P: ModalProvider
associatedtype C: View

var content: () -> C { get }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import SwiftUI

public struct DodamTimePickerPresenter<C: View>: ModalViewProtocol {

public typealias P = TimePickerProvider
private let hours = Array(0..<24)
private let minutes = Array(0..<60)

@StateObject private var provider: TimePickerProvider
@State private var size: CGSize = .zero
let content: () -> C

private let hours = Array(0..<24)
private let minutes = Array(0..<60)

init(
provider: TimePickerProvider,
@ViewBuilder content: @escaping () -> C
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import Foundation
import SwiftUI

public final class TimePickerProvider: ModalProvider {
@Published public var isPresent: Bool = false
@Published var isPresent: Bool = false

@Published var title: String = ""
@Published var hour: Int = 0
@Published var minute: Int = 0
Expand Down

0 comments on commit 3740991

Please sign in to comment.