import Foundation import Combine @MainActor public final class SessionStore: ObservableObject { @Published public private(set) var account: Account @Published public private(set) var authState: AuthState = .waitTdlibParameters @Published public private(set) var networkState: NetworkState = .ready @Published public private(set) var errorMessage: String? public let bridge: SessionBridge public init(account: Account, bridge: SessionBridge) { self.account = account self.bridge = bridge } public func refreshAuthState() async { do { authState = try await bridge.authState() errorMessage = nil } catch { errorMessage = error.localizedDescription } } public func apply(events: [SessionEvent]) { for event in events { if case let .authChanged(state) = event { authState = state } } } } @MainActor public final class AuthViewModel: ObservableObject { @Published public var phone = "" @Published public var code = "" @Published public var password = "" @Published public private(set) var isLoading = false @Published public private(set) var errorMessage: String? private let store: SessionStore public init(store: SessionStore) { self.store = store } public func submitCurrentStep() async { isLoading = true defer { isLoading = false } do { switch store.authState { case .waitPhoneNumber: try await store.bridge.sendPhoneNumber(phone) case .waitCode: try await store.bridge.sendCode(code) case .waitPassword: try await store.bridge.sendPassword(password) default: break } let events = try await store.bridge.pollEvents() store.apply(events: events) errorMessage = nil } catch { errorMessage = error.localizedDescription } } } @MainActor public final class ChatListViewModel: ObservableObject { @Published public private(set) var folders: [Folder] = [] @Published public private(set) var chats: [ChatSummary] = [] @Published public var selectedFolderId: Int32? @Published public var searchText = "" @Published public private(set) var isLoading = false @Published public private(set) var errorMessage: String? private let bridge: SessionBridge public init(bridge: SessionBridge) { self.bridge = bridge } public var filteredChats: [ChatSummary] { guard !searchText.isEmpty else { return chats } return chats.filter { chat in chat.title.localizedCaseInsensitiveContains(searchText) || (chat.username?.localizedCaseInsensitiveContains(searchText) ?? false) } } public func load() async { isLoading = true defer { isLoading = false } do { folders = try await bridge.loadFolders() chats = try await bridge.loadChats(folderId: selectedFolderId) errorMessage = nil } catch { errorMessage = error.localizedDescription } } } @MainActor public final class ChatViewModel: ObservableObject { @Published public private(set) var chat: ChatSummary @Published public private(set) var messages: [Message] = [] @Published public var composeText: String @Published public var replyTo: Message? @Published public private(set) var isLoading = false @Published public private(set) var errorMessage: String? private let bridge: SessionBridge public init(chat: ChatSummary, bridge: SessionBridge) { self.chat = chat self.bridge = bridge self.composeText = chat.draft?.text ?? "" } public func load() async { isLoading = true defer { isLoading = false } do { messages = try await bridge.loadHistory(chatId: chat.id) errorMessage = nil } catch { errorMessage = error.localizedDescription } } public func send() async { let text = composeText.trimmingCharacters(in: .whitespacesAndNewlines) guard !text.isEmpty else { return } do { let sent = try await bridge.sendMessage( chatId: chat.id, text: text, replyToMessageId: replyTo?.id ) messages.append(sent) composeText = "" replyTo = nil errorMessage = nil } catch { errorMessage = error.localizedDescription } } } @MainActor public final class ProfileViewModel: ObservableObject { @Published public private(set) var profile: Profile? @Published public private(set) var isLoading = false @Published public private(set) var errorMessage: String? private let bridge: SessionBridge public init(bridge: SessionBridge) { self.bridge = bridge } public func load(chatId: Int64) async { isLoading = true defer { isLoading = false } do { profile = try await bridge.openProfile(chatId: chatId) errorMessage = nil } catch { errorMessage = error.localizedDescription } } } @MainActor public final class MediaViewModel: ObservableObject { @Published public private(set) var activePhotoPath: String? @Published public private(set) var activeVoicePath: String? public init() {} public func showPhoto(path: String) { activePhotoPath = path } public func showVoice(path: String) { activeVoicePath = path } }