oficina / calendar Public

Clone
git clone https://amadan.net/oficina/calendar

Plain git — no account needed to clone.

Download

Download this file

1import RastrilloApple
2import SwiftUI
3
4struct EventEditor: View {
5 @ObservedObject var model: CalendarModel
6 let client: NativeHTTP
7 @State var event: CalendarEvent
8 @State private var start: Date
9 @State private var end: Date
10 @State private var scheduleChanged = false
11 @State private var wallClockFields: Bool
12 @State private var saving = false
13 @State private var error = ""
14 @Environment(\.dismiss) private var dismiss
15
16 init(model: CalendarModel, client: NativeHTTP, event: CalendarEvent) {
17 self.model = model; self.client = client
18 _event = State(initialValue: event)
19 let parsedStart = model.parse(event.start_local, allDay: event.all_day, zone: event.tz)
20 let parsedEnd = model.parse(event.end_local, allDay: event.all_day, zone: event.tz)
21 _wallClockFields = State(initialValue: parsedStart == nil || parsedEnd == nil)
22 _start = State(initialValue: parsedStart ?? model.date)
23 _end = State(initialValue: parsedEnd ?? model.date)
24 }
25 var body: some View {
26 NavigationStack {
27 Form {
28 Section {
29 TextField("Title", text: $event.title).accessibilityIdentifier("eventTitle")
30 TextField("Location", text: $event.location)
31 if event.id == 0 {
32 Picker("Calendar", selection: $event.calendar_id) {
33 ForEach(model.calendars.filter(\.writable)) { Text($0.name).tag($0.id) }
34 }
35 }
36 }
37 Section("When") {
38 Toggle("All day", isOn: $event.all_day).disabled(wallClockFields)
39 if wallClockFields {
40 TextField("Starts", text: $event.start_local)
41 TextField("Ends", text: $event.end_local)
42 Text("This event uses a local time during a clock change. Its original local dates are preserved; edit them as YYYY-MM-DDTHH:mm.").font(.footnote).foregroundStyle(.secondary)
43 } else {
44 DatePicker("Starts", selection: $start, displayedComponents: event.all_day ? [.date] : [.date, .hourAndMinute])
45 DatePicker(event.all_day ? "Ends before" : "Ends", selection: $end, displayedComponents: event.all_day ? [.date] : [.date, .hourAndMinute])
46 }
47 LabeledContent("Time zone", value: event.tz)
48 if event.all_day { Text("The end date is the first day after this event.").font(.footnote).foregroundStyle(.secondary) }
49 }
50 Section("Notes") { TextEditor(text: $event.notes).frame(minHeight: 100).accessibilityLabel("Notes") }
51 if !error.isEmpty { Section { Text(error).foregroundStyle(.red) } }
52 }.formStyle(.grouped).environment(\.timeZone, TimeZone(identifier: event.tz) ?? .gmt)
53 .navigationTitle(event.id == 0 ? "New event" : "Edit event")
54 .toolbar {
55 ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() }.disabled(saving) }
56 ToolbarItem(placement: .confirmationAction) {
57 Button(saving ? "Saving…" : "Save") {
58 if scheduleChanged && !wallClockFields {
59 event.start_local = model.format(start, allDay: event.all_day, zone: event.tz)
60 event.end_local = model.format(end, allDay: event.all_day, zone: event.tz)
61 }
62 saving = true
63 Task {
64 do { try await model.save(event, client: client); dismiss() }
65 catch { self.error = error.localizedDescription; saving = false }
66 }
67 }.disabled(saving || event.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || (!wallClockFields && end <= start))
68 }
69 }
70 }
71 #if os(macOS)
72 .frame(width: 500, height: 560)
73 #endif
74 .onChange(of: start) { _, _ in scheduleChanged = true }
75 .onChange(of: end) { _, _ in scheduleChanged = true }
76 .onChange(of: event.all_day) { _, allDay in
77 scheduleChanged = true
78 if allDay && !wallClockFields {
79 var calendar = Calendar(identifier: .gregorian)
80 calendar.timeZone = TimeZone(identifier: event.tz) ?? .gmt
81 start = calendar.startOfDay(for: start)
82 end = max(calendar.startOfDay(for: end), calendar.date(byAdding: .day, value: 1, to: start)!)
83 }
84 }
85 .interactiveDismissDisabled(saving)
86 }
87}
88