Share aus Chat
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -7,6 +7,7 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
269ECE662F92B5C700444B14 /* NahbarMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269ECE652F92B5C700444B14 /* NahbarMigration.swift */; };
|
||||||
26BB85B92F9248BD00889312 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BB85B82F9248BD00889312 /* SplashView.swift */; };
|
26BB85B92F9248BD00889312 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BB85B82F9248BD00889312 /* SplashView.swift */; };
|
||||||
26BB85BB2F924D9B00889312 /* StoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BB85BA2F924D9B00889312 /* StoreManager.swift */; };
|
26BB85BB2F924D9B00889312 /* StoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BB85BA2F924D9B00889312 /* StoreManager.swift */; };
|
||||||
26BB85BD2F924DB100889312 /* PaywallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BB85BC2F924DB100889312 /* PaywallView.swift */; };
|
26BB85BD2F924DB100889312 /* PaywallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26BB85BC2F924DB100889312 /* PaywallView.swift */; };
|
||||||
@@ -66,6 +67,7 @@
|
|||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
265F92202F9109B500CE0A5C /* nahbar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = nahbar.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
265F92202F9109B500CE0A5C /* nahbar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = nahbar.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
269ECE652F92B5C700444B14 /* NahbarMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NahbarMigration.swift; sourceTree = "<group>"; };
|
||||||
26BB85B82F9248BD00889312 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; };
|
26BB85B82F9248BD00889312 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; };
|
||||||
26BB85BA2F924D9B00889312 /* StoreManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreManager.swift; sourceTree = "<group>"; };
|
26BB85BA2F924D9B00889312 /* StoreManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreManager.swift; sourceTree = "<group>"; };
|
||||||
26BB85BC2F924DB100889312 /* PaywallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallView.swift; sourceTree = "<group>"; };
|
26BB85BC2F924DB100889312 /* PaywallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallView.swift; sourceTree = "<group>"; };
|
||||||
@@ -187,6 +189,7 @@
|
|||||||
26BB85C02F92525200889312 /* AIAnalysisService.swift */,
|
26BB85C02F92525200889312 /* AIAnalysisService.swift */,
|
||||||
26BB85C22F92586600889312 /* AIConfiguration.json */,
|
26BB85C22F92586600889312 /* AIConfiguration.json */,
|
||||||
26BB85C42F926A1C00889312 /* AppGroup.swift */,
|
26BB85C42F926A1C00889312 /* AppGroup.swift */,
|
||||||
|
269ECE652F92B5C700444B14 /* NahbarMigration.swift */,
|
||||||
);
|
);
|
||||||
path = nahbar;
|
path = nahbar;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -300,6 +303,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
269ECE662F92B5C700444B14 /* NahbarMigration.swift in Sources */,
|
||||||
26EF66322F9112E700824F91 /* Models.swift in Sources */,
|
26EF66322F9112E700824F91 /* Models.swift in Sources */,
|
||||||
26EF66332F9112E700824F91 /* TodayView.swift in Sources */,
|
26EF66332F9112E700824F91 /* TodayView.swift in Sources */,
|
||||||
26EF66412F9129F000824F91 /* CallWindowSetupView.swift in Sources */,
|
26EF66412F9129F000824F91 /* CallWindowSetupView.swift in Sources */,
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -36,10 +36,16 @@ enum AppGroup {
|
|||||||
|
|
||||||
/// Speichert eine Nachricht als ausstehenden Moment in der App Group.
|
/// Speichert eine Nachricht als ausstehenden Moment in der App Group.
|
||||||
/// Die Hauptapp importiert diese beim nächsten Start.
|
/// Die Hauptapp importiert diese beim nächsten Start.
|
||||||
static func enqueueMoment(personName: String, text: String, type: String) {
|
static func enqueueMoment(personName: String, text: String, type: String, source: String? = nil) {
|
||||||
var queue = pendingMoments
|
var queue = pendingMoments
|
||||||
queue.append(["personName": personName, "text": text, "type": type,
|
var entry: [String: String] = [
|
||||||
"createdAt": ISO8601DateFormatter().string(from: Date())])
|
"personName": personName,
|
||||||
|
"text": text,
|
||||||
|
"type": type,
|
||||||
|
"createdAt": ISO8601DateFormatter().string(from: Date())
|
||||||
|
]
|
||||||
|
if let source { entry["source"] = source }
|
||||||
|
queue.append(entry)
|
||||||
if let data = try? JSONSerialization.data(withJSONObject: queue) {
|
if let data = try? JSONSerialization.data(withJSONObject: queue) {
|
||||||
userDefaults.set(data, forKey: "pendingMoments")
|
userDefaults.set(data, forKey: "pendingMoments")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,8 +106,9 @@ struct ContentView: View {
|
|||||||
let text = entry["text"],
|
let text = entry["text"],
|
||||||
let typeRaw = entry["type"] else { continue }
|
let typeRaw = entry["type"] else { continue }
|
||||||
let type_ = MomentType(rawValue: typeRaw) ?? .conversation
|
let type_ = MomentType(rawValue: typeRaw) ?? .conversation
|
||||||
|
let source_ = entry["source"].flatMap { MomentSource(rawValue: $0) }
|
||||||
if let person = persons.first(where: { $0.name == name }) {
|
if let person = persons.first(where: { $0.name == name }) {
|
||||||
let moment = Moment(text: text, type: type_, person: person)
|
let moment = Moment(text: text, type: type_, source: source_, person: person)
|
||||||
modelContext.insert(moment)
|
modelContext.insert(moment)
|
||||||
person.moments?.append(moment)
|
person.moments?.append(moment)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import CoreData
|
||||||
|
|
||||||
// MARK: - AI Analysis State
|
// MARK: - AI Analysis State
|
||||||
|
|
||||||
@@ -60,6 +61,7 @@ private enum LogbuchItem: Identifiable {
|
|||||||
|
|
||||||
struct LogbuchView: View {
|
struct LogbuchView: View {
|
||||||
@Environment(\.nahbarTheme) var theme
|
@Environment(\.nahbarTheme) var theme
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
@StateObject private var store = StoreManager.shared
|
@StateObject private var store = StoreManager.shared
|
||||||
let person: Person
|
let person: Person
|
||||||
|
|
||||||
@@ -92,6 +94,14 @@ struct LogbuchView: View {
|
|||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.themedNavBar()
|
.themedNavBar()
|
||||||
.sheet(isPresented: $showPaywall) { PaywallView() }
|
.sheet(isPresented: $showPaywall) { PaywallView() }
|
||||||
|
.onReceive(
|
||||||
|
NotificationCenter.default.publisher(
|
||||||
|
for: Notification.Name("NSManagedObjectContextObjectsDidChangeNotification")
|
||||||
|
)
|
||||||
|
) { notification in
|
||||||
|
guard notification.userInfo?[NSInvalidatedAllObjectsKey] != nil else { return }
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if let cached = AIAnalysisService.shared.loadCached(for: person) {
|
if let cached = AIAnalysisService.shared.loadCached(for: person) {
|
||||||
analysisState = .result(cached.asResult, cached.analyzedAt)
|
analysisState = .result(cached.asResult, cached.analyzedAt)
|
||||||
|
|||||||
@@ -55,6 +55,24 @@ enum MomentType: String, CaseIterable, Codable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum MomentSource: String, CaseIterable, Codable {
|
||||||
|
case whatsapp = "WhatsApp"
|
||||||
|
case imessage = "iMessage"
|
||||||
|
case telegram = "Telegram"
|
||||||
|
case signal = "Signal"
|
||||||
|
case other = "Chat"
|
||||||
|
|
||||||
|
var icon: String {
|
||||||
|
switch self {
|
||||||
|
case .whatsapp: return "message.fill"
|
||||||
|
case .imessage: return "message.fill"
|
||||||
|
case .telegram: return "paperplane.fill"
|
||||||
|
case .signal: return "lock.circle.fill"
|
||||||
|
case .other: return "bubble.left.fill"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Person
|
// MARK: - Person
|
||||||
|
|
||||||
@Model
|
@Model
|
||||||
@@ -218,13 +236,15 @@ class Moment {
|
|||||||
var id: UUID = UUID()
|
var id: UUID = UUID()
|
||||||
var text: String = ""
|
var text: String = ""
|
||||||
var typeRaw: String = MomentType.conversation.rawValue
|
var typeRaw: String = MomentType.conversation.rawValue
|
||||||
|
var sourceRaw: String? = nil
|
||||||
var createdAt: Date = Date()
|
var createdAt: Date = Date()
|
||||||
var person: Person?
|
var person: Person?
|
||||||
|
|
||||||
init(text: String, type: MomentType = .conversation, person: Person? = nil) {
|
init(text: String, type: MomentType = .conversation, source: MomentSource? = nil, person: Person? = nil) {
|
||||||
self.id = UUID()
|
self.id = UUID()
|
||||||
self.text = text
|
self.text = text
|
||||||
self.typeRaw = type.rawValue
|
self.typeRaw = type.rawValue
|
||||||
|
self.sourceRaw = source?.rawValue
|
||||||
self.createdAt = Date()
|
self.createdAt = Date()
|
||||||
self.person = person
|
self.person = person
|
||||||
}
|
}
|
||||||
@@ -233,4 +253,9 @@ class Moment {
|
|||||||
get { MomentType(rawValue: typeRaw) ?? .conversation }
|
get { MomentType(rawValue: typeRaw) ?? .conversation }
|
||||||
set { typeRaw = newValue.rawValue }
|
set { typeRaw = newValue.rawValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var source: MomentSource? {
|
||||||
|
get { sourceRaw.flatMap { MomentSource(rawValue: $0) } }
|
||||||
|
set { sourceRaw = newValue?.rawValue }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ struct NahbarApp: App {
|
|||||||
.onAppear { applyTabBarAppearance(activeTheme) }
|
.onAppear { applyTabBarAppearance(activeTheme) }
|
||||||
.onChange(of: activeThemeIDRaw) { _, _ in applyTabBarAppearance(activeTheme) }
|
.onChange(of: activeThemeIDRaw) { _, _ in applyTabBarAppearance(activeTheme) }
|
||||||
}
|
}
|
||||||
.modelContainer(AppGroup.makeMainContainer())
|
.modelContainer(AppGroup.makeMainContainerWithMigration())
|
||||||
.onChange(of: scenePhase) { _, phase in
|
.onChange(of: scenePhase) { _, phase in
|
||||||
if phase == .background {
|
if phase == .background {
|
||||||
appLockManager.lockIfEnabled()
|
appLockManager.lockIfEnabled()
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import SwiftData
|
||||||
|
|
||||||
|
// MARK: - Schema V1 (Originalschema – Moment ohne sourceRaw)
|
||||||
|
//
|
||||||
|
// Diese Typen spiegeln exakt das ursprüngliche Schema wider, bevor
|
||||||
|
// Moment.sourceRaw hinzugefügt wurde. SwiftData vergleicht das
|
||||||
|
// gespeicherte Schema-Hash mit dieser Definition und führt bei
|
||||||
|
// Übereinstimmung die Lightweight-Migration zu V2 durch.
|
||||||
|
|
||||||
|
enum NahbarSchemaV1: VersionedSchema {
|
||||||
|
static var versionIdentifier = Schema.Version(1, 0, 0)
|
||||||
|
static var models: [any PersistentModel.Type] {
|
||||||
|
[Person.self, Moment.self, LogEntry.self]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Model final class Person {
|
||||||
|
var id: UUID = UUID()
|
||||||
|
var name: String = ""
|
||||||
|
var tagRaw: String = "Andere"
|
||||||
|
var birthday: Date? = nil
|
||||||
|
var occupation: String? = nil
|
||||||
|
var location: String? = nil
|
||||||
|
var interests: String? = nil
|
||||||
|
var generalNotes: String? = nil
|
||||||
|
var nudgeFrequencyRaw: String = "Monatlich"
|
||||||
|
var photoData: Data? = nil
|
||||||
|
var nextStep: String? = nil
|
||||||
|
var nextStepCompleted: Bool = false
|
||||||
|
var nextStepReminderDate: Date? = nil
|
||||||
|
var lastSuggestedForCall: Date? = nil
|
||||||
|
var createdAt: Date = Date()
|
||||||
|
@Relationship(deleteRule: .cascade) var moments: [Moment]? = []
|
||||||
|
@Relationship(deleteRule: .cascade) var logEntries: [LogEntry]? = []
|
||||||
|
|
||||||
|
init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Model final class Moment {
|
||||||
|
var id: UUID = UUID()
|
||||||
|
var text: String = ""
|
||||||
|
var typeRaw: String = "Gespräch"
|
||||||
|
var createdAt: Date = Date()
|
||||||
|
var person: Person? = nil
|
||||||
|
|
||||||
|
init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Model final class LogEntry {
|
||||||
|
var id: UUID = UUID()
|
||||||
|
var typeRaw: String = "Schritt abgeschlossen"
|
||||||
|
var title: String = ""
|
||||||
|
var loggedAt: Date = Date()
|
||||||
|
var person: Person? = nil
|
||||||
|
|
||||||
|
init() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Schema V2 (aktuell – Moment mit sourceRaw)
|
||||||
|
|
||||||
|
enum NahbarSchemaV2: VersionedSchema {
|
||||||
|
static var versionIdentifier = Schema.Version(2, 0, 0)
|
||||||
|
static var models: [any PersistentModel.Type] {
|
||||||
|
// Verweist auf die aktuellen Top-Level-Modeltypen in Models.swift
|
||||||
|
[nahbar.Person.self, nahbar.Moment.self, nahbar.LogEntry.self]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Migrationsplan V1 → V2
|
||||||
|
|
||||||
|
enum NahbarMigrationPlan: SchemaMigrationPlan {
|
||||||
|
static var schemas: [any VersionedSchema.Type] {
|
||||||
|
[NahbarSchemaV1.self, NahbarSchemaV2.self]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lightweight: SwiftData ergänzt sourceRaw mit nil für alle bestehenden Momente.
|
||||||
|
static var stages: [MigrationStage] {
|
||||||
|
[.lightweight(fromVersion: NahbarSchemaV1.self, toVersion: NahbarSchemaV2.self)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Container-Erstellung (nur Hauptapp, nicht Share Extension)
|
||||||
|
|
||||||
|
extension AppGroup {
|
||||||
|
/// Erstellt den ModelContainer mit automatischer Schemamigration.
|
||||||
|
/// Bei Nutzern, die bereits Daten haben, ergänzt SwiftData das neue
|
||||||
|
/// Feld sourceRaw = nil für alle vorhandenen Momente – kein Datenverlust.
|
||||||
|
static func makeMainContainerWithMigration() -> ModelContainer {
|
||||||
|
let schema = Schema([nahbar.Person.self, nahbar.Moment.self, nahbar.LogEntry.self])
|
||||||
|
let icloudEnabled = UserDefaults.standard.bool(forKey: icloudSyncKey)
|
||||||
|
let cloudKit: ModelConfiguration.CloudKitDatabase = icloudEnabled ? .automatic : .none
|
||||||
|
|
||||||
|
// Versuch 1: gewünschte Konfiguration mit Migrationsplan
|
||||||
|
let config = ModelConfiguration(schema: schema, cloudKitDatabase: cloudKit)
|
||||||
|
if let container = try? ModelContainer(
|
||||||
|
for: schema,
|
||||||
|
migrationPlan: NahbarMigrationPlan.self,
|
||||||
|
configurations: [config]
|
||||||
|
) { return container }
|
||||||
|
|
||||||
|
// Versuch 2: lokal ohne CloudKit mit Migrationsplan
|
||||||
|
let localConfig = ModelConfiguration(schema: schema, cloudKitDatabase: .none)
|
||||||
|
if let container = try? ModelContainer(
|
||||||
|
for: schema,
|
||||||
|
migrationPlan: NahbarMigrationPlan.self,
|
||||||
|
configurations: [localConfig]
|
||||||
|
) { return container }
|
||||||
|
|
||||||
|
// Letzter Ausweg: nur im Speicher (sollte nie eintreten)
|
||||||
|
return try! ModelContainer(for: schema, configurations: [ModelConfiguration(isStoredInMemoryOnly: true)])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SwiftData
|
import SwiftData
|
||||||
|
import CoreData
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
|
||||||
struct PersonDetailView: View {
|
struct PersonDetailView: View {
|
||||||
@Environment(\.nahbarTheme) var theme
|
@Environment(\.nahbarTheme) var theme
|
||||||
@Environment(\.modelContext) var modelContext
|
@Environment(\.modelContext) var modelContext
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
@Bindable var person: Person
|
@Bindable var person: Person
|
||||||
|
|
||||||
@State private var showingAddMoment = false
|
@State private var showingAddMoment = false
|
||||||
@@ -51,6 +53,16 @@ struct PersonDetailView: View {
|
|||||||
.onAppear {
|
.onAppear {
|
||||||
nextStepText = person.nextStep ?? ""
|
nextStepText = person.nextStep ?? ""
|
||||||
}
|
}
|
||||||
|
// Schützt vor Crash wenn der ModelContext durch Migration oder
|
||||||
|
// CloudKit-Sync intern reset() aufruft und Person-Objekte ungültig werden.
|
||||||
|
.onReceive(
|
||||||
|
NotificationCenter.default.publisher(
|
||||||
|
for: Notification.Name("NSManagedObjectContextObjectsDidChangeNotification")
|
||||||
|
)
|
||||||
|
) { notification in
|
||||||
|
guard notification.userInfo?[NSInvalidatedAllObjectsKey] != nil else { return }
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Header
|
// MARK: - Header
|
||||||
@@ -412,11 +424,26 @@ struct MomentRowView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .top, spacing: 12) {
|
HStack(alignment: .top, spacing: 12) {
|
||||||
Image(systemName: moment.type.icon)
|
// Type icon with optional source badge overlay
|
||||||
.font(.system(size: 13, weight: .light))
|
ZStack(alignment: .bottomTrailing) {
|
||||||
.foregroundStyle(theme.contentTertiary)
|
Image(systemName: moment.type.icon)
|
||||||
.frame(width: 18)
|
.font(.system(size: 13, weight: .light))
|
||||||
.padding(.top, 2)
|
.foregroundStyle(theme.contentTertiary)
|
||||||
|
.frame(width: 18)
|
||||||
|
.padding(.top, 2)
|
||||||
|
|
||||||
|
if let source = moment.source {
|
||||||
|
Image(systemName: source.icon)
|
||||||
|
.font(.system(size: 8, weight: .semibold))
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.padding(2)
|
||||||
|
.background(sourceColor(source))
|
||||||
|
.clipShape(Circle())
|
||||||
|
.offset(x: 5, y: 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(width: 18)
|
||||||
|
.padding(.top, 2)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(moment.text)
|
Text(moment.text)
|
||||||
@@ -424,9 +451,20 @@ struct MomentRowView: View {
|
|||||||
.foregroundStyle(theme.contentPrimary)
|
.foregroundStyle(theme.contentPrimary)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
|
||||||
Text(moment.createdAt, format: .dateTime.day().month(.abbreviated).year().locale(Locale(identifier: "de_DE")))
|
HStack(spacing: 6) {
|
||||||
.font(.system(size: 12))
|
Text(moment.createdAt, format: .dateTime.day().month(.abbreviated).year().locale(Locale(identifier: "de_DE")))
|
||||||
.foregroundStyle(theme.contentTertiary)
|
.font(.system(size: 12))
|
||||||
|
.foregroundStyle(theme.contentTertiary)
|
||||||
|
|
||||||
|
if let source = moment.source {
|
||||||
|
Text("·")
|
||||||
|
.font(.system(size: 12))
|
||||||
|
.foregroundStyle(theme.contentTertiary)
|
||||||
|
Text(source.rawValue)
|
||||||
|
.font(.system(size: 12))
|
||||||
|
.foregroundStyle(sourceColor(source).opacity(0.8))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -434,6 +472,16 @@ struct MomentRowView: View {
|
|||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
.padding(.vertical, 12)
|
.padding(.vertical, 12)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func sourceColor(_ source: MomentSource) -> Color {
|
||||||
|
switch source {
|
||||||
|
case .whatsapp: return Color(red: 0.15, green: 0.80, blue: 0.33)
|
||||||
|
case .imessage: return Color(red: 0.0, green: 0.48, blue: 1.0)
|
||||||
|
case .telegram: return Color(red: 0.17, green: 0.67, blue: 0.94)
|
||||||
|
case .signal: return Color(red: 0.23, green: 0.47, blue: 0.95)
|
||||||
|
case .other: return Color.gray
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Info Row
|
// MARK: - Info Row
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ struct ShareExtensionView: View {
|
|||||||
|
|
||||||
@State private var text: String
|
@State private var text: String
|
||||||
@State private var momentType: MomentType = .conversation
|
@State private var momentType: MomentType = .conversation
|
||||||
|
@State private var momentSource: MomentSource = .other
|
||||||
@State private var people: [CachedPerson] = []
|
@State private var people: [CachedPerson] = []
|
||||||
@State private var selectedPerson: CachedPerson?
|
@State private var selectedPerson: CachedPerson?
|
||||||
@State private var searchText = ""
|
@State private var searchText = ""
|
||||||
@@ -50,6 +51,15 @@ struct ShareExtensionView: View {
|
|||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section("Herkunft") {
|
||||||
|
Picker("Messenger", selection: $momentSource) {
|
||||||
|
ForEach(MomentSource.allCases, id: \.self) { source in
|
||||||
|
Label(source.rawValue, systemImage: source.icon).tag(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.menu)
|
||||||
|
}
|
||||||
|
|
||||||
Section("Kontakt") {
|
Section("Kontakt") {
|
||||||
if people.isEmpty {
|
if people.isEmpty {
|
||||||
Text("Keine Kontakte gefunden. Öffne nahbar einmal, damit die Kontakte hier erscheinen.")
|
Text("Keine Kontakte gefunden. Öffne nahbar einmal, damit die Kontakte hier erscheinen.")
|
||||||
@@ -132,7 +142,7 @@ struct ShareExtensionView: View {
|
|||||||
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
guard !trimmed.isEmpty else { return }
|
guard !trimmed.isEmpty else { return }
|
||||||
isSaving = true
|
isSaving = true
|
||||||
AppGroup.enqueueMoment(personName: person.name, text: trimmed, type: momentType.rawValue)
|
AppGroup.enqueueMoment(personName: person.name, text: trimmed, type: momentType.rawValue, source: momentSource.rawValue)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user