iOS: Ablauf-Benachrichtigungen, je Server einstellbar
Die App zeigte "Bald ablaufend" nur beim Oeffnen. Jetzt erinnert sie aktiv vor dem Ablauf - mit frei zusammenstellbaren Regeln, pro Server getrennt. Eine Regel ist "X Tage/Wochen vor Ablauf", optional nur fuer eine Kategorie (inklusive deren Unterkategorien), einmalig oder taeglich wiederkehrend. Eine Kategorie-Regel ersetzt fuer ihre Produkte die allgemeine - so kann Molkerei frueher warnen als Chips. Dazu am Ablauftag eine letzte, deutliche Warnung fuer alle Produkte (eigener Schalter). Die Meldungen kommen als Tages-Sammelmeldung, je Server eine, zur pro Server eingestellten Uhrzeit. Weil Vorrania im Heimnetz laeuft und von unterwegs oft nicht erreichbar ist, kann die App nicht zuverlaessig im Hintergrund abfragen - und iOS verlangt ohnehin, dass lokale Benachrichtigungen im Voraus geplant werden. Also: aus den Ablaufdaten kuenftige Termine berechnen und einplanen; aufgefrischt beim Oeffnen, im Vordergrund und best-effort per Hintergrund-Aktualisierung. Geplant wird fuer alle eingerichteten Server, jeder mit seinem Token aus dem Keychain; unerreichbare werden uebersprungen. iOS erlaubt 64 ausstehende Meldungen je App, deshalb die naechstliegenden zuerst und bei 60 gekappt. Einstellungen sitzen hinter der Glocke je Server in der Server-Verwaltung (nicht admin-beschraenkt, es ist eine Geraete-Einstellung). Getippt wechselt die Meldung auf den betroffenen Server und oeffnet "Bald ablaufend". Keine Backend-Aenderung: die Kategorie kommt aus /products, der Ablauf aus /expiring. Die Rechenlogik (ExpiryPlanner) ist bewusst netz- und iOS-frei und mit einem eigenstaendigen Swift-Harnisch geprueft: Kategorie-Vorrang, Unterkategorien, Rueckfall auf die allgemeine Regel, einmalig vs. wiederkehrend und die letzte Warnung. Modelle gegen echte /expiring- und /products-Antworten abgeglichen, App startet ohne Absturz (auch die BGTask-Registrierung). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -46,17 +46,31 @@ struct ServerListView: View {
|
||||
@State private var editing: ServerProfile?
|
||||
@State private var addShown = false
|
||||
@State private var pendingDeletion: ServerProfile?
|
||||
@State private var notifyFor: ServerProfile?
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
Section {
|
||||
ForEach(session.profiles) { profile in
|
||||
Button {
|
||||
session.switchTo(profile.id)
|
||||
dismiss()
|
||||
} label: {
|
||||
row(for: profile)
|
||||
HStack(spacing: 8) {
|
||||
// Zeile wechselt den Server, die Glocke oeffnet die
|
||||
// Benachrichtigungen - zwei getrennte Ziele in einer Zeile.
|
||||
Button {
|
||||
session.switchTo(profile.id)
|
||||
dismiss()
|
||||
} label: {
|
||||
row(for: profile)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Button {
|
||||
notifyFor = profile
|
||||
} label: {
|
||||
Image(systemName: NotificationStore.config(for: profile.id).enabled
|
||||
? "bell.fill" : "bell")
|
||||
.foregroundStyle(Color.accentColor)
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button("Löschen", role: .destructive) { pendingDeletion = profile }
|
||||
@@ -64,7 +78,7 @@ struct ServerListView: View {
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text("Jeder Server merkt sich seine eigene Anmeldung. Ein Wechsel meldet dich nicht ab.")
|
||||
Text("Jeder Server merkt sich seine eigene Anmeldung. Ein Wechsel meldet dich nicht ab. Die Glocke stellt Ablauf-Erinnerungen ein.")
|
||||
}
|
||||
|
||||
if session.profiles.isEmpty {
|
||||
@@ -88,6 +102,16 @@ struct ServerListView: View {
|
||||
.sheet(item: $editing) { profile in
|
||||
ServerEditView(profile: profile)
|
||||
}
|
||||
.sheet(item: $notifyFor) { profile in
|
||||
NavigationStack {
|
||||
NotificationSettingsView(profileID: profile.id, serverName: profile.name)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
Button("Fertig") { notifyFor = nil }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirmationDialog(
|
||||
pendingDeletion.map { "„\($0.name)“ löschen?" } ?? "",
|
||||
isPresented: Binding(get: { pendingDeletion != nil },
|
||||
|
||||
Reference in New Issue
Block a user