feat: iOS Gruppenansicht direkt im Kalender (Umschalter + Banner)
Gruppen sind nicht mehr nur im Menü versteckt: im Top-Bar gibt es einen
Gruppen-Umschalter (Persönlich / <Gruppe>). Beim Wählen einer Gruppe zeigt
der echte Monats-/Wochen-/Tagesansicht die kombinierte Überlagerung
(GET /groups/{id}/combined) mit server-definierten Farben und
Besitzer-Präfix; ein Banner "Gruppenansicht: <Name>" mit "Verlassen".
CalendarStore.activeGroup steuert den Modus.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,9 @@ class CalendarStore {
|
||||
var lastError: String? = nil
|
||||
var weekStartsOnMonday = true
|
||||
var writableCalendars: [WritableCalendar] = []
|
||||
// When set, the calendar shows the group's combined overlay instead of the
|
||||
// user's own events. nil = personal view.
|
||||
var activeGroup: CalGroup? = nil
|
||||
|
||||
/// Set of `"source:calendarId"` keys the user has chosen to hide from the
|
||||
/// calendar views. Persisted in UserDefaults as a JSON array. Events whose
|
||||
@@ -239,6 +242,20 @@ class CalendarStore {
|
||||
/// unless `force` is set (used after create/edit to pull fresh server data
|
||||
/// for the visible range, bypassing the cache).
|
||||
func loadEvents(api: CalendarrAPI, start: Date, end: Date, force: Bool = false) async {
|
||||
// Group overlay mode: always fetch the combined view for the range
|
||||
// (no personal cache), decorating each event with its owner.
|
||||
if let g = activeGroup {
|
||||
isLoading = true
|
||||
lastError = nil
|
||||
defer { isLoading = false }
|
||||
do {
|
||||
let fetched = try await api.fetchGroupCombined(groupId: g.id, start: start, end: end)
|
||||
events = fetched.map { decorateGroupEvent($0) }
|
||||
} catch {
|
||||
lastError = error.localizedDescription
|
||||
}
|
||||
return
|
||||
}
|
||||
if !force, isCached(start: start, end: end) {
|
||||
refreshFromCache(start: start, end: end)
|
||||
return
|
||||
@@ -255,6 +272,21 @@ class CalendarStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Prefix a combined-view event with its owner (others) or 👥 + creator
|
||||
/// (group calendar). Colour comes from the server's display_color.
|
||||
private func decorateGroupEvent(_ ev: CalEvent) -> CalEvent {
|
||||
var e = ev
|
||||
let me = UserDefaults.standard.integer(forKey: "userId")
|
||||
func first(_ s: String) -> String { s.split(separator: " ").first.map(String.init) ?? s }
|
||||
if ev.isGroupEvent {
|
||||
if let c = ev.creator, c.id != me { e.title = "👥 \(first(c.displayName)): \(ev.title)" }
|
||||
else { e.title = "👥 \(ev.title)" }
|
||||
} else if let o = ev.owner, o.id != me {
|
||||
e.title = "\(first(o.displayName)): \(ev.title)"
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
/// Background prefetch for ±months around today – called once on startup.
|
||||
func prefetchBackground(api: CalendarrAPI, months: Int) async {
|
||||
let cal = userCalendar
|
||||
|
||||
@@ -154,6 +154,9 @@ private let strings: [String: [String: String]] = [
|
||||
"common.info": "Info",
|
||||
"common.done": "Fertig",
|
||||
"groups.title": "Gruppen",
|
||||
"groups.personal": "Persönlich",
|
||||
"groups.view_label": "Gruppenansicht",
|
||||
"groups.exit": "Verlassen",
|
||||
"groups.none": "Noch keine Gruppen",
|
||||
"groups.combined_empty": "Keine Termine in diesem Zeitraum",
|
||||
"group.create": "Gruppe erstellen",
|
||||
@@ -452,6 +455,9 @@ private let strings: [String: [String: String]] = [
|
||||
"common.info": "Info",
|
||||
"common.done": "Done",
|
||||
"groups.title": "Groups",
|
||||
"groups.personal": "Personal",
|
||||
"groups.view_label": "Group view",
|
||||
"groups.exit": "Exit",
|
||||
"groups.none": "No groups yet",
|
||||
"groups.combined_empty": "No events in this period",
|
||||
"group.create": "Create group",
|
||||
|
||||
Reference in New Issue
Block a user