aboutsummaryrefslogtreecommitdiff
path: root/Hotline/State
diff options
context:
space:
mode:
Diffstat (limited to 'Hotline/State')
-rw-r--r--Hotline/State/AppState.swift58
-rw-r--r--Hotline/State/FilePreviewState.swift207
-rw-r--r--Hotline/State/ServerState.swift4
3 files changed, 266 insertions, 3 deletions
diff --git a/Hotline/State/AppState.swift b/Hotline/State/AppState.swift
index 3917ad0..558af2a 100644
--- a/Hotline/State/AppState.swift
+++ b/Hotline/State/AppState.swift
@@ -12,8 +12,64 @@ final class AppState {
}
- var activeHotline: Hotline? = nil
+ var activeHotline: HotlineState? = nil
var activeServerState: ServerState? = nil
var cloudKitReady: Bool = false
+
+ // MARK: - Transfers
+
+ /// All active transfers across all servers
+ /// Transfers persist even if you disconnect from the server
+ var transfers: [TransferInfo] = []
+
+ /// Track download tasks by reference number for cancellation
+ @ObservationIgnored private var transferTasks: [UUID: Task<Void, Never>] = [:]
+
+ /// Add a transfer to the transfer list
+ @MainActor
+ func addTransfer(_ transfer: TransferInfo) {
+ self.transfers.append(transfer)
+ }
+
+ /// Cancel a transfer by transfer ID
+ @MainActor
+ func cancelTransfer(id: UUID) {
+ guard let transferIndex = self.transfers.firstIndex(where: { $0.id == id }) else {
+ return
+ }
+
+ // Cancel the task if it exists
+ if let task = self.transferTasks[id] {
+ task.cancel()
+ self.transferTasks.removeValue(forKey: id)
+ }
+
+ // Remove from transfers list
+ self.transfers.remove(at: transferIndex)
+ }
+
+ /// Cancel all active transfers
+ @MainActor
+ func cancelAllTransfers() {
+ for (_, task) in self.transferTasks {
+ task.cancel()
+ }
+ self.transferTasks.removeAll()
+
+ // Clear transfers
+ self.transfers.removeAll()
+ }
+
+ /// Register a transfer task
+ @MainActor
+ func registerTransferTask(_ task: Task<Void, Never>, transferID: UUID) {
+ self.transferTasks[transferID] = task
+ }
+
+ /// Unregister a download task (called on completion/failure)
+ @MainActor
+ func unregisterTransferTask(for transferID: UUID) {
+ self.transferTasks.removeValue(forKey: transferID)
+ }
}
diff --git a/Hotline/State/FilePreviewState.swift b/Hotline/State/FilePreviewState.swift
new file mode 100644
index 0000000..97bd907
--- /dev/null
+++ b/Hotline/State/FilePreviewState.swift
@@ -0,0 +1,207 @@
+//
+// FilePreviewState.swift
+// Hotline
+//
+// Modern file preview state using HotlineFilePreviewClientNew
+//
+
+import SwiftUI
+import UniformTypeIdentifiers
+
+// MARK: - Preview Type
+
+enum FilePreviewType: Equatable {
+ case unknown
+ case image
+ case text
+}
+
+/// State for a file preview download
+@MainActor
+@Observable
+final class FilePreviewState {
+ // MARK: - Properties
+
+ let info: PreviewFileInfo
+
+ private var previewClient: HotlineFilePreviewClientNew?
+ private var previewTask: Task<Void, Never>?
+
+ var state: LoadState = .unloaded
+ var progress: Double = 0.0
+
+ var fileURL: URL? = nil
+
+ #if os(iOS)
+ var image: UIImage? = nil
+ #elseif os(macOS)
+ var image: NSImage? = nil
+ #endif
+
+ var text: String? = nil
+ var styledText: NSAttributedString? = nil
+
+ // MARK: - Computed Properties
+
+ var previewType: FilePreviewType {
+ info.previewType
+ }
+
+ // MARK: - Initialization
+
+ init(info: PreviewFileInfo) {
+ self.info = info
+ }
+
+ nonisolated deinit {
+ // Note: Can't access @MainActor properties from deinit
+ // Cleanup will happen when previewClient is deallocated
+ }
+
+ // MARK: - Public API
+
+ func download() {
+ // Cancel any existing download
+ previewTask?.cancel()
+ previewClient?.cleanup()
+
+ let task = Task { @MainActor in
+ do {
+ let client = HotlineFilePreviewClientNew(
+ fileName: info.name,
+ address: info.address,
+ port: UInt16(info.port),
+ reference: info.id,
+ size: UInt32(info.size)
+ )
+ self.previewClient = client
+
+ self.state = .loading
+ self.progress = 0.0
+
+ let url = try await client.preview { [weak self] progress in
+ guard let self else { return }
+
+ Task { @MainActor in
+ switch progress {
+ case .preparing:
+ self.state = .loading
+ self.progress = 0.0
+
+ case .connecting:
+ self.state = .loading
+ self.progress = 0.0
+
+ case .connected:
+ self.state = .loading
+ self.progress = 0.0
+
+ case .transfer(name: _, size: _, total: _, progress: let p, speed: _, estimate: _):
+ self.state = .loading
+ self.progress = p
+
+ case .completed(url: let url):
+ self.state = .loaded
+ self.progress = 1.0
+ self.fileURL = url
+// self.loadPreview(from: url)
+
+ case .error(let error):
+ self.state = .failed
+ self.progress = 0.0
+ print("FilePreviewState: Download failed: \(error)")
+
+ case .unconnected:
+ break
+ }
+ }
+ }
+
+ // Final load if not already loaded
+ if self.state != .loaded {
+ self.state = .loaded
+ self.progress = 1.0
+ self.fileURL = url
+// self.loadPreview(from: url)
+ }
+
+ } catch is CancellationError {
+ // Cancelled, do nothing
+ return
+ } catch {
+ self.state = .failed
+ self.progress = 0.0
+ print("FilePreviewState: Download error: \(error)")
+ }
+ }
+
+ self.previewTask = task
+ }
+
+ func cancel() {
+ previewTask?.cancel()
+ previewTask = nil
+ previewClient?.cancel()
+ }
+
+ func cleanup() {
+ previewClient?.cleanup()
+ previewClient = nil
+ fileURL = nil
+ image = nil
+ text = nil
+ styledText = nil
+ }
+
+ // MARK: - Private Implementation
+
+ private func loadPreview(from url: URL) {
+ guard let data = try? Data(contentsOf: url) else {
+ self.state = .failed
+ print("FilePreviewState: Failed to read preview data from \(url.path)")
+ return
+ }
+
+ switch self.previewType {
+ case .image:
+ #if os(iOS)
+ self.image = UIImage(data: data)
+ #elseif os(macOS)
+ self.image = NSImage(data: data)
+ #endif
+
+ if self.image == nil {
+ self.state = .failed
+ print("FilePreviewState: Failed to create image from data")
+ }
+
+ case .text:
+ let encoding: UInt = NSString.stringEncoding(for: data, convertedString: nil, usedLossyConversion: nil)
+ if encoding != 0 {
+ self.text = String(data: data, encoding: String.Encoding(rawValue: encoding))
+ } else {
+ self.text = String(data: data, encoding: .utf8)
+ }
+
+ if self.text == nil {
+ self.state = .failed
+ print("FilePreviewState: Failed to decode text data")
+ }
+
+ case .unknown:
+ print("FilePreviewState: Unknown preview type for \(info.name)")
+ break
+ }
+ }
+}
+
+// MARK: - Load State
+
+extension FilePreviewState {
+ enum LoadState: Equatable {
+ case unloaded
+ case loading
+ case loaded
+ case failed
+ }
+}
diff --git a/Hotline/State/ServerState.swift b/Hotline/State/ServerState.swift
index e2fa40f..5913bf5 100644
--- a/Hotline/State/ServerState.swift
+++ b/Hotline/State/ServerState.swift
@@ -5,8 +5,8 @@ class ServerState: Equatable {
var id: UUID = UUID()
var selection: ServerNavigationType
var serverName: String? = nil
- var serverBanner: NSImage? = nil
- var bannerColors: ColorArt? = nil
+// var serverBanner: NSImage? = nil
+// var bannerBackgroundColor: Color? = nil
init(selection: ServerNavigationType) {
self.selection = selection