1// ChatViewModel.swift
2// SophaxChat
3//
4// Observable view model for an individual conversation (DM).
5// Holds message-input and conversation state so ChatView stays
6// focused on layout only.
7
8import Foundation
9import Observation
10import PhotosUI
11import SophaxChatCore
12
13@MainActor
14@Observable
15final class ChatViewModel {
16
17 // Message input
18 var messageText: String = ""
19 var typingTask: Task<Void, Never>? = nil
20
21 // Message actions
22 var replyingTo: StoredMessage? = nil
23 var editingMessage: StoredMessage? = nil
24 var forwardingMessage: StoredMessage? = nil
25
26 // Search
27 var isSearching: Bool = false
28 var searchQuery: String = ""
29
30 // Attachments
31 var photoPickerItem: PhotosPickerItem? = nil
32
33 // Disappearing messages
34 var disappearingInterval: DisappearingInterval = .off
35
36 // Input buffers for dialogs
37 var renameText: String = ""
38 var deadDropText: String = ""
39
40 // AI sheet seed
41 var aiSeedPrompt: String? = nil
42
43 private let disappearingKey: String
44 private let draftKey: String
45
46 init(peerID: String) {
47 self.disappearingKey = "com.sophax.disappearingInterval.\(peerID)"
48 self.draftKey = "com.sophax.draft.\(peerID)"
49 let ud = UserDefaults.standard
50 if let raw = ud.string(forKey: disappearingKey) {
51 disappearingInterval = DisappearingInterval(rawValue: raw) ?? .off
52 }
53 messageText = ud.string(forKey: draftKey) ?? ""
54 }
55
56 func saveDraft() {
57 if messageText.isEmpty {
58 UserDefaults.standard.removeObject(forKey: draftKey)
59 } else {
60 UserDefaults.standard.set(messageText, forKey: draftKey)
61 }
62 }
63
64 func clearDraft() {
65 UserDefaults.standard.removeObject(forKey: draftKey)
66 }
67
68 func saveDisappearing() {
69 UserDefaults.standard.set(disappearingInterval.rawValue, forKey: disappearingKey)
70 }
71
72 func cancelReply() { replyingTo = nil }
73
74 func cancelEdit() {
75 editingMessage = nil
76 messageText = ""
77 }
78}