chore(repo): iOS-App als ios/ ins Hauptrepo uebernehmen
Calendarr-IOS wird mit vollstaendiger Historie als Unterverzeichnis ios/ eingebunden. Damit liegen Server, Web, Android und iOS in einem gemeinsamen Repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import SwiftUI
|
||||
|
||||
/// Minimal "new birthday" mask for the single birthday calendar: enter a name
|
||||
/// and a date (optionally "year unknown"). Saves an all-day, yearly-recurring
|
||||
/// local event; the server adds the age suffix and cake icon on read. If no
|
||||
/// birthday calendar exists yet, offers to activate one.
|
||||
struct BirthdayEditorSheet: View {
|
||||
let api: CalendarrAPI
|
||||
var onDone: () async -> Void
|
||||
|
||||
@AppStorage("appLanguage") private var appLang = "system"
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var calendar: LocalCalendar? = nil
|
||||
@State private var name = ""
|
||||
@State private var date = Date()
|
||||
@State private var yearUnknown = false
|
||||
@State private var loading = true
|
||||
@State private var saving = false
|
||||
@State private var activating = false
|
||||
|
||||
private var canSave: Bool {
|
||||
!saving && calendar != nil
|
||||
&& !name.trimmingCharacters(in: .whitespaces).isEmpty
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
if loading {
|
||||
HStack { Spacer(); ProgressView(); Spacer() }
|
||||
} else if calendar == nil {
|
||||
Section {
|
||||
Text(L10n.t("birthday.activate_hint", appLang))
|
||||
.foregroundStyle(.secondary)
|
||||
Button(L10n.t("birthday.activate", appLang)) { Task { await activate() } }
|
||||
.disabled(activating)
|
||||
}
|
||||
} else {
|
||||
Section(L10n.t("birthday.person", appLang)) {
|
||||
TextField(L10n.t("birthday.person_placeholder", appLang), text: $name)
|
||||
}
|
||||
Section(L10n.t("birthday.date", appLang)) {
|
||||
DatePicker(L10n.t("birthday.date", appLang), selection: $date,
|
||||
displayedComponents: [.date])
|
||||
Toggle(L10n.t("birthday.year_unknown", appLang), isOn: $yearUnknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(L10n.t("birthday.new", appLang))
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(L10n.t("common.cancel", appLang)) { dismiss() }
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(L10n.t("event.save", appLang)) { Task { await save() } }
|
||||
.disabled(!canSave)
|
||||
}
|
||||
}
|
||||
.task { await load() }
|
||||
}
|
||||
}
|
||||
|
||||
private func load() async {
|
||||
calendar = await BirthdaysImporter.birthdayCalendar(api: api)
|
||||
loading = false
|
||||
}
|
||||
|
||||
private func activate() async {
|
||||
activating = true
|
||||
calendar = await BirthdaysImporter.ensureBirthdayCalendar(api: api)
|
||||
activating = false
|
||||
}
|
||||
|
||||
private func save() async {
|
||||
guard let cal = calendar else { return }
|
||||
saving = true
|
||||
let calc = Calendar.current
|
||||
let comps = calc.dateComponents([.year, .month, .day], from: date)
|
||||
let month = comps.month ?? 1
|
||||
let day = comps.day ?? 1
|
||||
let year = yearUnknown ? nil : comps.year
|
||||
// All-day anchor at local noon; anchor year = birth year (else 1970) so
|
||||
// the yearly rule expands across any queried range.
|
||||
var anchor = DateComponents()
|
||||
anchor.year = year ?? 1970
|
||||
anchor.month = month
|
||||
anchor.day = day
|
||||
anchor.hour = 12
|
||||
let start = calc.date(from: anchor) ?? date
|
||||
let end = calc.date(byAdding: .day, value: 1, to: start) ?? start
|
||||
_ = try? await api.createLocalEvent(
|
||||
calendarId: cal.id, title: name.trimmingCharacters(in: .whitespaces),
|
||||
start: start, end: end, isAllDay: true, location: "", description: "",
|
||||
color: nil, rrule: "FREQ=YEARLY", birthYear: year
|
||||
)
|
||||
await onDone()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user