Skip to content

Commit 5d93fe9

Browse files
committed
ui(settings): switch export and reprocess date pickers to inline calendars
1 parent 66bcd65 commit 5d93fe9

1 file changed

Lines changed: 107 additions & 54 deletions

File tree

Dayflow/Dayflow/Views/UI/Settings/SettingsOtherTabView.swift

Lines changed: 107 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ struct SettingsOtherTabView: View {
44
@ObservedObject var viewModel: OtherSettingsViewModel
55
@ObservedObject var launchAtLoginManager: LaunchAtLoginManager
66
@FocusState private var isOutputLanguageFocused: Bool
7-
@State private var isExportStartDatePickerPresented = false
8-
@State private var isExportEndDatePickerPresented = false
9-
@State private var isReprocessDatePickerPresented = false
7+
@State private var activeExportDatePicker: ExportDatePicker?
8+
@State private var isReprocessDatePickerExpanded = false
9+
10+
private enum ExportDatePicker {
11+
case start
12+
case end
13+
}
1014

1115
var body: some View {
1216
VStack(alignment: .leading, spacing: 28) {
@@ -142,14 +146,16 @@ struct SettingsOtherTabView: View {
142146

143147
VStack(alignment: .leading, spacing: 14) {
144148
HStack(alignment: .bottom, spacing: 12) {
145-
datePopoverField(
149+
datePillField(
146150
label: "From",
147-
date: $viewModel.exportStartDate,
148-
isPresented: $isExportStartDatePickerPresented,
151+
date: viewModel.exportStartDate,
152+
isExpanded: activeExportDatePicker == .start,
149153
accessibilityLabel: "Export start date",
150-
onOpen: {
151-
isExportEndDatePickerPresented = false
152-
isReprocessDatePickerPresented = false
154+
onTap: {
155+
withAnimation(.spring(response: 0.24, dampingFraction: 0.88)) {
156+
activeExportDatePicker = activeExportDatePicker == .start ? nil : .start
157+
isReprocessDatePickerExpanded = false
158+
}
153159
}
154160
)
155161

@@ -158,16 +164,31 @@ struct SettingsOtherTabView: View {
158164
.foregroundColor(.black.opacity(0.35))
159165
.padding(.bottom, 12)
160166

161-
datePopoverField(
167+
datePillField(
162168
label: "To",
163-
date: $viewModel.exportEndDate,
164-
isPresented: $isExportEndDatePickerPresented,
169+
date: viewModel.exportEndDate,
170+
isExpanded: activeExportDatePicker == .end,
165171
accessibilityLabel: "Export end date",
166-
onOpen: {
167-
isExportStartDatePickerPresented = false
168-
isReprocessDatePickerPresented = false
172+
onTap: {
173+
withAnimation(.spring(response: 0.24, dampingFraction: 0.88)) {
174+
activeExportDatePicker = activeExportDatePicker == .end ? nil : .end
175+
isReprocessDatePickerExpanded = false
176+
}
177+
}
178+
)
179+
}
180+
181+
if let activeExportDatePicker {
182+
inlineCalendarField(
183+
label: activeExportDatePicker == .start ? "Start date" : "End date",
184+
date: exportDateBinding(for: activeExportDatePicker),
185+
onDateSelected: {
186+
withAnimation(.spring(response: 0.24, dampingFraction: 0.88)) {
187+
self.activeExportDatePicker = nil
188+
}
169189
}
170190
)
191+
.transition(.move(edge: .top).combined(with: .opacity))
171192
}
172193

173194
Text("Includes titles, summaries, and details for each card.")
@@ -231,17 +252,34 @@ struct SettingsOtherTabView: View {
231252

232253
VStack(alignment: .leading, spacing: 14) {
233254
VStack(alignment: .leading, spacing: 8) {
234-
datePopoverField(
255+
datePillField(
235256
label: "Day",
236-
date: $viewModel.reprocessDayDate,
237-
isPresented: $isReprocessDatePickerPresented,
257+
date: viewModel.reprocessDayDate,
258+
isExpanded: isReprocessDatePickerExpanded,
238259
accessibilityLabel: "Reprocess day",
239260
disabled: viewModel.isReprocessingDay,
240-
onOpen: {
241-
isExportStartDatePickerPresented = false
242-
isExportEndDatePickerPresented = false
261+
onTap: {
262+
withAnimation(.spring(response: 0.24, dampingFraction: 0.88)) {
263+
isReprocessDatePickerExpanded.toggle()
264+
activeExportDatePicker = nil
265+
}
243266
}
244267
)
268+
269+
if isReprocessDatePickerExpanded {
270+
inlineCalendarField(
271+
label: "Day",
272+
date: $viewModel.reprocessDayDate,
273+
disabled: viewModel.isReprocessingDay,
274+
onDateSelected: {
275+
withAnimation(.spring(response: 0.24, dampingFraction: 0.88)) {
276+
isReprocessDatePickerExpanded = false
277+
}
278+
}
279+
)
280+
.transition(.move(edge: .top).combined(with: .opacity))
281+
}
282+
245283
Text(dayString)
246284
.font(.custom("Nunito", size: 12))
247285
.foregroundColor(.black.opacity(0.5))
@@ -308,14 +346,22 @@ struct SettingsOtherTabView: View {
308346
Self.dateLabelFormatter.string(from: timelineDisplayDate(from: date))
309347
}
310348

311-
@ViewBuilder
312-
private func datePopoverField(
349+
private func exportDateBinding(for picker: ExportDatePicker) -> Binding<Date> {
350+
switch picker {
351+
case .start:
352+
return $viewModel.exportStartDate
353+
case .end:
354+
return $viewModel.exportEndDate
355+
}
356+
}
357+
358+
private func datePillField(
313359
label: String,
314-
date: Binding<Date>,
315-
isPresented: Binding<Bool>,
360+
date: Date,
361+
isExpanded: Bool,
316362
accessibilityLabel: String,
317363
disabled: Bool = false,
318-
onOpen: @escaping () -> Void
364+
onTap: @escaping () -> Void
319365
) -> some View {
320366
VStack(alignment: .leading, spacing: 7) {
321367
Text(label)
@@ -324,21 +370,20 @@ struct SettingsOtherTabView: View {
324370

325371
Button {
326372
guard !disabled else { return }
327-
onOpen()
328-
isPresented.wrappedValue.toggle()
373+
onTap()
329374
} label: {
330375
HStack(spacing: 10) {
331376
Image(systemName: "calendar")
332377
.font(.system(size: 12, weight: .semibold))
333378
.foregroundColor(Color(red: 0.25, green: 0.17, blue: 0).opacity(disabled ? 0.4 : 0.75))
334379

335-
Text(formattedTimelineDate(date.wrappedValue))
380+
Text(formattedTimelineDate(date))
336381
.font(.custom("Nunito", size: 14))
337382
.foregroundColor(.black.opacity(disabled ? 0.35 : 0.82))
338383

339384
Spacer(minLength: 4)
340385

341-
Image(systemName: isPresented.wrappedValue ? "chevron.up" : "chevron.down")
386+
Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
342387
.font(.system(size: 11, weight: .semibold))
343388
.foregroundColor(.black.opacity(disabled ? 0.2 : 0.35))
344389
}
@@ -351,7 +396,7 @@ struct SettingsOtherTabView: View {
351396
.overlay(
352397
RoundedRectangle(cornerRadius: 10)
353398
.stroke(
354-
isPresented.wrappedValue
399+
isExpanded
355400
? Color(hex: "F9C36B")
356401
: Color(hex: "FFE0A5"),
357402
lineWidth: 1.2
@@ -363,32 +408,40 @@ struct SettingsOtherTabView: View {
363408
.buttonStyle(.plain)
364409
.disabled(disabled)
365410
.accessibilityLabel(Text(accessibilityLabel))
366-
.popover(isPresented: isPresented, arrowEdge: .bottom) {
367-
VStack(alignment: .leading, spacing: 10) {
368-
HStack {
369-
Text(label)
370-
.font(.custom("Nunito", size: 13))
371-
.foregroundColor(.black.opacity(0.65))
372-
Spacer()
373-
Button("Done") {
374-
isPresented.wrappedValue = false
375-
}
376-
.buttonStyle(.plain)
377-
.font(.custom("Nunito", size: 12))
378-
.foregroundColor(Color(red: 0.25, green: 0.17, blue: 0))
379-
}
411+
}
412+
}
380413

381-
DatePicker("", selection: date, displayedComponents: .date)
382-
.datePickerStyle(.graphical)
383-
.labelsHidden()
384-
.onChange(of: date.wrappedValue) { _, _ in
385-
isPresented.wrappedValue = false
386-
}
414+
private func inlineCalendarField(
415+
label: String,
416+
date: Binding<Date>,
417+
disabled: Bool = false,
418+
onDateSelected: @escaping () -> Void
419+
) -> some View {
420+
VStack(alignment: .leading, spacing: 6) {
421+
Text(label)
422+
.font(.custom("Nunito", size: 11.5))
423+
.foregroundColor(.black.opacity(0.5))
424+
425+
DatePicker("", selection: date, displayedComponents: .date)
426+
.datePickerStyle(.graphical)
427+
.labelsHidden()
428+
.onChange(of: date.wrappedValue) { _, _ in
429+
onDateSelected()
387430
}
388-
.padding(14)
389-
.frame(width: 300)
390-
}
431+
.frame(maxWidth: 290, alignment: .leading)
432+
.padding(10)
433+
.background(
434+
RoundedRectangle(cornerRadius: 12)
435+
.fill(Color.white.opacity(disabled ? 0.45 : 0.82))
436+
.overlay(
437+
RoundedRectangle(cornerRadius: 12)
438+
.stroke(Color(hex: "FFE0A5"), lineWidth: 1.2)
439+
)
440+
)
441+
.shadow(color: .black.opacity(disabled ? 0 : 0.04), radius: 7, x: 0, y: 2)
442+
.disabled(disabled)
391443
}
444+
.opacity(disabled ? 0.7 : 1)
392445
}
393446

394447
private static let dateLabelFormatter: DateFormatter = {

0 commit comments

Comments
 (0)