From c359caab725ad98145b40f4fcd79ac2851dc8726 Mon Sep 17 00:00:00 2001 From: Sven Date: Fri, 17 Apr 2026 16:50:52 +0200 Subject: [PATCH] Guter Zwischenstand --- nahbar/.DS_Store | Bin 6148 -> 6148 bytes .../UserInterfaceState.xcuserstate | Bin 37342 -> 40173 bytes nahbar/nahbar/AddMomentView.swift | 4 +- nahbar/nahbar/AppGroup.swift | 74 +++++++++++++++--- nahbar/nahbar/ContentView.swift | 39 ++++++++- nahbar/nahbar/Models.swift | 39 +++++---- nahbar/nahbar/NahbarApp.swift | 2 +- nahbar/nahbar/PersonDetailView.swift | 2 +- nahbar/nahbar/SettingsView.swift | 44 ++++++++++- nahbar/nahbar/nahbar.entitlements | 10 +++ .../ShareExtensionView.swift | 72 ++++++----------- 11 files changed, 202 insertions(+), 84 deletions(-) diff --git a/nahbar/.DS_Store b/nahbar/.DS_Store index 4ff7e18466f87daa18185797d7671ad9e1fa8704..e23afc69af0d0f6f0238f5acf4e63a4c9d948d57 100644 GIT binary patch delta 19 acmZoMXffCj&cvi;Jb5CM+U9zuR#5;wSOx0< delta 19 acmZoMXffCj&cr0IGI=7C+U9zuR#5;vNd?gW diff --git a/nahbar/nahbar.xcodeproj/project.xcworkspace/xcuserdata/sven.xcuserdatad/UserInterfaceState.xcuserstate b/nahbar/nahbar.xcodeproj/project.xcworkspace/xcuserdata/sven.xcuserdatad/UserInterfaceState.xcuserstate index 5c19e1d3a0b7477e7d707bf6325df3195d7748ae..c64b83932dc324b0de6e62a645bbf2deb2ef6246 100644 GIT binary patch delta 21660 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 diff --git a/nahbar/nahbar/AddMomentView.swift b/nahbar/nahbar/AddMomentView.swift index e12e64d..83c94be 100644 --- a/nahbar/nahbar/AddMomentView.swift +++ b/nahbar/nahbar/AddMomentView.swift @@ -187,7 +187,7 @@ struct AddMomentView: View { let moment = Moment(text: trimmed, type: selectedType, person: person) modelContext.insert(moment) - person.moments.append(moment) + person.moments?.append(moment) guard addToCalendar else { dismiss() @@ -201,7 +201,7 @@ struct AddMomentView: View { person: person ) modelContext.insert(calEntry) - person.logEntries.append(calEntry) + person.logEntries?.append(calEntry) // Kein async/await — Callback-API vermeidet "unsafeForcedSync" createCalendarEvent(notes: trimmed) diff --git a/nahbar/nahbar/AppGroup.swift b/nahbar/nahbar/AppGroup.swift index 0074081..de4e55d 100644 --- a/nahbar/nahbar/AppGroup.swift +++ b/nahbar/nahbar/AppGroup.swift @@ -2,21 +2,75 @@ import Foundation import SwiftData /// Gemeinsame App-Group-Konfiguration für Hauptapp und Share Extension. -/// Die App-Group-ID muss in beiden Targets als Capability eingetragen sein. enum AppGroup { static let identifier = "group.nahbar.shared" - /// URL des geteilten Containers. Fällt auf das Documents-Verzeichnis zurück, - /// falls die App Group noch nicht eingerichtet ist (z.B. in frühen Dev-Builds). - static var containerURL: URL { - FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: identifier) - ?? FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + /// Shared UserDefaults für die Kommunikation zwischen Hauptapp und Extension. + static var userDefaults: UserDefaults { + UserDefaults(suiteName: identifier) ?? .standard } - static func makeModelContainer() throws -> ModelContainer { + // MARK: - Hauptapp: Standard-Store (Daten bleiben erhalten) + + /// ModelContainer für die Hauptapp – nutzt Standard-Speicherort (Daten-kompatibel). + /// Versucht CloudKit; fällt bei Bedarf auf lokalen Store zurück. + static let icloudSyncKey = "icloudSyncEnabled" + + static func makeMainContainer() -> ModelContainer { let schema = Schema([Person.self, Moment.self, LogEntry.self]) - let storeURL = containerURL.appendingPathComponent("nahbar.store") - let config = ModelConfiguration(schema: schema, url: storeURL) - return try ModelContainer(for: schema, configurations: [config]) + let icloudEnabled = UserDefaults.standard.bool(forKey: icloudSyncKey) + let cloudKit: ModelConfiguration.CloudKitDatabase = icloudEnabled ? .automatic : .none + let config = ModelConfiguration(schema: schema, cloudKitDatabase: cloudKit) + if let container = try? ModelContainer(for: schema, configurations: [config]) { + return container + } + // Fallback: lokal ohne CloudKit + let localConfig = ModelConfiguration(schema: schema, cloudKitDatabase: .none) + if let container = try? ModelContainer(for: schema, configurations: [localConfig]) { + return container + } + return try! ModelContainer(for: schema, configurations: [ModelConfiguration(isStoredInMemoryOnly: true)]) + } + + // MARK: - Share Extension: App-Group-Store (nur für Extension) + + /// Speichert eine Nachricht als ausstehenden Moment in der App Group. + /// Die Hauptapp importiert diese beim nächsten Start. + static func enqueueMoment(personName: String, text: String, type: String) { + var queue = pendingMoments + queue.append(["personName": personName, "text": text, "type": type, + "createdAt": ISO8601DateFormatter().string(from: Date())]) + if let data = try? JSONSerialization.data(withJSONObject: queue) { + userDefaults.set(data, forKey: "pendingMoments") + } + } + + static var pendingMoments: [[String: String]] { + guard let data = userDefaults.data(forKey: "pendingMoments"), + let array = try? JSONSerialization.jsonObject(with: data) as? [[String: String]] + else { return [] } + return array + } + + static func clearPendingMoments() { + userDefaults.removeObject(forKey: "pendingMoments") + } + + // MARK: - Personenliste für Extension + + /// Hauptapp schreibt beim Start die Personenliste in UserDefaults, + /// damit die Extension sie ohne Store-Zugriff lesen kann. + static func savePeopleList(_ people: [Person]) { + let list = people.map { ["id": $0.id.uuidString, "name": $0.name, "tag": $0.tagRaw] } + if let data = try? JSONSerialization.data(withJSONObject: list) { + userDefaults.set(data, forKey: "cachedPeople") + } + } + + static var cachedPeople: [[String: String]] { + guard let data = userDefaults.data(forKey: "cachedPeople"), + let array = try? JSONSerialization.jsonObject(with: data) as? [[String: String]] + else { return [] } + return array } } diff --git a/nahbar/nahbar/ContentView.swift b/nahbar/nahbar/ContentView.swift index 2887551..ff40e88 100644 --- a/nahbar/nahbar/ContentView.swift +++ b/nahbar/nahbar/ContentView.swift @@ -54,11 +54,13 @@ struct ContentView: View { suggestionDateStr = ISO8601DateFormatter().string(from: Date()) let entry = LogEntry(type: .call, title: "Anruf mit \(person.firstName)", person: person) modelContext.insert(entry) - person.logEntries.append(entry) + person.logEntries?.append(entry) } } } .onAppear { + syncPeopleCache() + importPendingMoments() if !onboardingDone { showingOnboarding = true } else { @@ -66,7 +68,14 @@ struct ContentView: View { } } .onChange(of: scenePhase) { _, phase in - if phase == .active { checkCallWindow() } + if phase == .active { + syncPeopleCache() + importPendingMoments() + checkCallWindow() + } + } + .onChange(of: persons) { _, _ in + syncPeopleCache() } } @@ -83,6 +92,32 @@ struct ContentView: View { } } + /// Schreibt die aktuelle Personenliste in den App-Group-Cache für die Share Extension. + private func syncPeopleCache() { + AppGroup.savePeopleList(persons) + } + + /// Importiert Momente, die über die Share Extension eingereiht wurden. + private func importPendingMoments() { + let pending = AppGroup.pendingMoments + guard !pending.isEmpty else { return } + for entry in pending { + guard let name = entry["personName"], + let text = entry["text"], + let typeRaw = entry["type"] else { continue } + let type_ = MomentType(rawValue: typeRaw) ?? .conversation + if let person = persons.first(where: { $0.name == name }) { + let moment = Moment(text: text, type: type_, person: person) + modelContext.insert(moment) + person.moments?.append(moment) + } + } + if !pending.isEmpty { + try? modelContext.save() + AppGroup.clearPendingMoments() + } + } + private var suggestionShownToday: Bool { guard !suggestionDateStr.isEmpty, let date = ISO8601DateFormatter().date(from: suggestionDateStr) else { return false } diff --git a/nahbar/nahbar/Models.swift b/nahbar/nahbar/Models.swift index 9c0a765..d2f3ef8 100644 --- a/nahbar/nahbar/Models.swift +++ b/nahbar/nahbar/Models.swift @@ -59,23 +59,23 @@ enum MomentType: String, CaseIterable, Codable { @Model class Person { - var id: UUID - var name: String - var tagRaw: String + var id: UUID = UUID() + var name: String = "" + var tagRaw: String = PersonTag.other.rawValue var birthday: Date? var occupation: String? var location: String? var interests: String? var generalNotes: String? - var nudgeFrequencyRaw: String + var nudgeFrequencyRaw: String = NudgeFrequency.monthly.rawValue var photoData: Data? var nextStep: String? - var nextStepCompleted: Bool + var nextStepCompleted: Bool = false var nextStepReminderDate: Date? var lastSuggestedForCall: Date? - var createdAt: Date - @Relationship(deleteRule: .cascade) var moments: [Moment] - @Relationship(deleteRule: .cascade) var logEntries: [LogEntry] + var createdAt: Date = Date() + @Relationship(deleteRule: .cascade) var moments: [Moment]? = [] + @Relationship(deleteRule: .cascade) var logEntries: [LogEntry]? = [] init( name: String, @@ -117,7 +117,7 @@ class Person { } var lastMomentDate: Date? { - moments.sorted { $0.createdAt > $1.createdAt }.first?.createdAt + (moments ?? []).sorted { $0.createdAt > $1.createdAt }.first?.createdAt } var needsAttention: Bool { @@ -125,7 +125,6 @@ class Person { if let last = lastMomentDate { return Date().timeIntervalSince(last) > Double(days * 86400) } - // Never had a moment – show if added more than `days` ago return Date().timeIntervalSince(createdAt) > Double(days * 86400) } @@ -156,11 +155,11 @@ class Person { } var sortedMoments: [Moment] { - moments.sorted { $0.createdAt > $1.createdAt } + (moments ?? []).sorted { $0.createdAt > $1.createdAt } } var sortedLogEntries: [LogEntry] { - logEntries.sorted { $0.loggedAt > $1.loggedAt } + (logEntries ?? []).sorted { $0.loggedAt > $1.loggedAt } } } @@ -192,10 +191,10 @@ enum LogEntryType: String, Codable { @Model class LogEntry { - var id: UUID - var typeRaw: String - var title: String - var loggedAt: Date + var id: UUID = UUID() + var typeRaw: String = LogEntryType.nextStep.rawValue + var title: String = "" + var loggedAt: Date = Date() var person: Person? init(type: LogEntryType, title: String, person: Person? = nil) { @@ -216,10 +215,10 @@ class LogEntry { @Model class Moment { - var id: UUID - var text: String - var typeRaw: String - var createdAt: Date + var id: UUID = UUID() + var text: String = "" + var typeRaw: String = MomentType.conversation.rawValue + var createdAt: Date = Date() var person: Person? init(text: String, type: MomentType = .conversation, person: Person? = nil) { diff --git a/nahbar/nahbar/NahbarApp.swift b/nahbar/nahbar/NahbarApp.swift index e373dcd..7becc21 100644 --- a/nahbar/nahbar/NahbarApp.swift +++ b/nahbar/nahbar/NahbarApp.swift @@ -43,7 +43,7 @@ struct NahbarApp: App { .onAppear { applyTabBarAppearance(activeTheme) } .onChange(of: activeThemeIDRaw) { _, _ in applyTabBarAppearance(activeTheme) } } - .modelContainer(try! AppGroup.makeModelContainer()) + .modelContainer(AppGroup.makeMainContainer()) .onChange(of: scenePhase) { _, phase in if phase == .background { appLockManager.lockIfEnabled() diff --git a/nahbar/nahbar/PersonDetailView.swift b/nahbar/nahbar/PersonDetailView.swift index 638eab4..9801e6a 100644 --- a/nahbar/nahbar/PersonDetailView.swift +++ b/nahbar/nahbar/PersonDetailView.swift @@ -164,7 +164,7 @@ struct PersonDetailView: View { if let step = person.nextStep { let entry = LogEntry(type: .nextStep, title: step, person: person) modelContext.insert(entry) - person.logEntries.append(entry) + person.logEntries?.append(entry) } person.nextStepCompleted = true cancelReminder(for: person) diff --git a/nahbar/nahbar/SettingsView.swift b/nahbar/nahbar/SettingsView.swift index 47f8fbc..730b3bd 100644 --- a/nahbar/nahbar/SettingsView.swift +++ b/nahbar/nahbar/SettingsView.swift @@ -8,6 +8,7 @@ struct SettingsView: View { @EnvironmentObject private var appLockManager: AppLockManager @AppStorage("upcomingDaysAhead") private var daysAhead: Int = 7 + @AppStorage("icloudSyncEnabled") private var icloudSyncEnabled: Bool = false @AppStorage("aiBaseURL") private var aiBaseURL: String = AIConfig.fallback.baseURL @AppStorage("aiAPIKey") private var aiAPIKey: String = AIConfig.fallback.apiKey @AppStorage("aiModel") private var aiModel: String = AIConfig.fallback.model @@ -289,6 +290,47 @@ struct SettingsView: View { .padding(.horizontal, 20) } + // iCloud + VStack(alignment: .leading, spacing: 12) { + SectionHeader(title: "iCloud", icon: "icloud") + .padding(.horizontal, 20) + + VStack(spacing: 0) { + HStack { + VStack(alignment: .leading, spacing: 2) { + Text("iCloud-Backup") + .font(.system(size: 15)) + .foregroundStyle(theme.contentPrimary) + Text(icloudSyncEnabled + ? "Daten werden mit iCloud synchronisiert" + : "Daten werden nur lokal gespeichert") + .font(.system(size: 12)) + .foregroundStyle(theme.contentTertiary) + } + Spacer() + Toggle("", isOn: $icloudSyncEnabled) + .tint(theme.accent) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + + RowDivider() + HStack(spacing: 8) { + Image(systemName: "info.circle") + .font(.system(size: 12)) + .foregroundStyle(theme.contentTertiary) + Text("Änderung wird beim nächsten App-Start übernommen.") + .font(.system(size: 12)) + .foregroundStyle(theme.contentTertiary) + } + .padding(.horizontal, 16) + .padding(.vertical, 10) + } + .background(theme.surfaceCard) + .clipShape(RoundedRectangle(cornerRadius: theme.radiusCard)) + .padding(.horizontal, 20) + } + // About VStack(alignment: .leading, spacing: 12) { SectionHeader(title: "Über nahbar", icon: "info.circle") @@ -297,8 +339,6 @@ struct SettingsView: View { VStack(spacing: 0) { SettingsInfoRow(label: "Version", value: "1.0 Draft") RowDivider() - SettingsInfoRow(label: "Daten", value: "Lokal + iCloud") - RowDivider() SettingsInfoRow(label: "Datenschutz", value: "Deine Daten verlassen nicht dein Gerät") } .background(theme.surfaceCard) diff --git a/nahbar/nahbar/nahbar.entitlements b/nahbar/nahbar/nahbar.entitlements index b75ec07..7e62b33 100644 --- a/nahbar/nahbar/nahbar.entitlements +++ b/nahbar/nahbar/nahbar.entitlements @@ -2,6 +2,16 @@ + aps-environment + development + com.apple.developer.icloud-container-identifiers + + iCloud.Team.nahbar + + com.apple.developer.icloud-services + + CloudKit + com.apple.security.application-groups group.nahbar.shared diff --git a/nahbar/nahbarShareExtension/ShareExtensionView.swift b/nahbar/nahbarShareExtension/ShareExtensionView.swift index 75bbbbc..b18600c 100644 --- a/nahbar/nahbarShareExtension/ShareExtensionView.swift +++ b/nahbar/nahbarShareExtension/ShareExtensionView.swift @@ -1,5 +1,11 @@ import SwiftUI -import SwiftData + +/// Einfache Personen-Darstellung für die Share Extension (aus UserDefaults-Cache). +struct CachedPerson: Identifiable, Equatable { + let id: UUID + let name: String + let tag: String +} struct ShareExtensionView: View { let sharedText: String @@ -7,8 +13,8 @@ struct ShareExtensionView: View { @State private var text: String @State private var momentType: MomentType = .conversation - @State private var people: [Person] = [] - @State private var selectedPerson: Person? + @State private var people: [CachedPerson] = [] + @State private var selectedPerson: CachedPerson? @State private var searchText = "" @State private var isSaving = false @State private var errorMessage: String? @@ -19,7 +25,7 @@ struct ShareExtensionView: View { self._text = State(initialValue: sharedText) } - private var filteredPeople: [Person] { + private var filteredPeople: [CachedPerson] { searchText.isEmpty ? people : people.filter { $0.name.localizedCaseInsensitiveContains(searchText) } @@ -46,14 +52,15 @@ struct ShareExtensionView: View { Section("Kontakt") { if people.isEmpty { - Text("Keine Kontakte gefunden") + Text("Keine Kontakte gefunden. Öffne nahbar einmal, damit die Kontakte hier erscheinen.") .foregroundStyle(.secondary) .font(.system(size: 14)) } else { TextField("Suchen…", text: $searchText) ForEach(filteredPeople) { person in PersonPickerRow( - person: person, + name: person.name, + tag: person.tag, isSelected: selectedPerson?.id == person.id ) { selectedPerson = (selectedPerson?.id == person.id) ? nil : person @@ -88,7 +95,8 @@ struct ShareExtensionView: View { // MARK: - Subviews struct PersonPickerRow: View { - let person: Person + let name: String + let tag: String let isSelected: Bool let onTap: () -> Void @@ -96,18 +104,12 @@ struct ShareExtensionView: View { Button(action: onTap) { HStack { VStack(alignment: .leading, spacing: 2) { - Text(person.name) - .foregroundStyle(.primary) - if let tag = PersonTag(rawValue: person.tagRaw) { - Text(tag.rawValue) - .font(.caption) - .foregroundStyle(.secondary) - } + Text(name).foregroundStyle(.primary) + Text(tag).font(.caption).foregroundStyle(.secondary) } Spacer() if isSelected { - Image(systemName: "checkmark") - .foregroundStyle(Color.accentColor) + Image(systemName: "checkmark").foregroundStyle(Color.accentColor) } } } @@ -118,41 +120,19 @@ struct ShareExtensionView: View { // MARK: - Data private func loadPeople() { - do { - let container = try AppGroup.makeModelContainer() - let context = ModelContext(container) - let descriptor = FetchDescriptor(sortBy: [SortDescriptor(\.name)]) - people = (try? context.fetch(descriptor)) ?? [] - } catch { - errorMessage = "Kontakte konnten nicht geladen werden." + people = AppGroup.cachedPeople.compactMap { dict in + guard let idString = dict["id"], let id = UUID(uuidString: idString), + let name = dict["name"], let tag = dict["tag"] else { return nil } + return CachedPerson(id: id, name: name, tag: tag) } } private func save() { guard let person = selectedPerson else { return } + let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return } isSaving = true - errorMessage = nil - do { - let container = try AppGroup.makeModelContainer() - let context = ModelContext(container) - let personID = person.id - let descriptor = FetchDescriptor(predicate: #Predicate { $0.id == personID }) - guard let target = try context.fetch(descriptor).first else { - errorMessage = "Kontakt nicht gefunden." - isSaving = false - return - } - let moment = Moment( - text: text.trimmingCharacters(in: .whitespacesAndNewlines), - type: momentType, - person: target - ) - context.insert(moment) - try context.save() - onDismiss() - } catch { - errorMessage = "Speichern fehlgeschlagen: \(error.localizedDescription)" - } - isSaving = false + AppGroup.enqueueMoment(personName: person.name, text: trimmed, type: momentType.rawValue) + onDismiss() } }