Skip to content

Commit

Permalink
Add image preview to Files so you don't have to download images to vi…
Browse files Browse the repository at this point in the history
…ew them, works with animated GIFs too. Added cmd-R shortcut for Servers window. More work on login view.
  • Loading branch information
mierau committed Jan 6, 2024
1 parent 57c33eb commit 2f8f71d
Show file tree
Hide file tree
Showing 16 changed files with 720 additions and 205 deletions.
12 changes: 8 additions & 4 deletions Hotline.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
DA4F2BF82B16A17200D8ADDC /* HotlineProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F2BF72B16A17200D8ADDC /* HotlineProtocol.swift */; };
DA4F2C012B1A558E00D8ADDC /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F2C002B1A558E00D8ADDC /* ChatView.swift */; platformFilter = ios; };
DA5753682B33E88A00FAC277 /* HotlineFileClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5753672B33E88A00FAC277 /* HotlineFileClient.swift */; };
DA57536A2B34A60D00FAC277 /* FileImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5753692B34A60D00FAC277 /* FileImageView.swift */; };
DA57536C2B36BA1D00FAC277 /* TextDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA57536B2B36BA1D00FAC277 /* TextDocument.swift */; };
DA6300972B24036B0034CBFD /* HotlineClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA6300962B24036B0034CBFD /* HotlineClient.swift */; };
DA77253F2B21176D006C5ABB /* NewsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA77253E2B21176D006C5ABB /* NewsView.swift */; platformFilter = ios; };
Expand All @@ -32,6 +31,8 @@
DA9CAFC42B126D5800CDA197 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA9CAFC32B126D5800CDA197 /* Preview Assets.xcassets */; };
DA9CAFCB2B126E3300CDA197 /* HotlineTrackerClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9CAFCA2B126E3300CDA197 /* HotlineTrackerClient.swift */; };
DAAEE66B2B3FBC2100A5BA07 /* TransferInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAAEE66A2B3FBC2100A5BA07 /* TransferInfo.swift */; };
DAAEE66D2B475F1400A5BA07 /* PreviewFileInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAAEE66C2B475F1400A5BA07 /* PreviewFileInfo.swift */; };
DAAEE66F2B47625600A5BA07 /* FilePreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAAEE66E2B47625600A5BA07 /* FilePreviewView.swift */; platformFilters = (macos, ); };
DABFCC292B1530DC009F40D2 /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DABFCC282B1530DC009F40D2 /* FoundationExtensions.swift */; };
DAC002192B21630900A6C290 /* SwiftUIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC002182B21630900A6C290 /* SwiftUIExtensions.swift */; };
DADDB28B2B22B31F0024040D /* Tracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADDB28A2B22B31F0024040D /* Tracker.swift */; };
Expand Down Expand Up @@ -65,7 +66,6 @@
DA4F2BF72B16A17200D8ADDC /* HotlineProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlineProtocol.swift; sourceTree = "<group>"; };
DA4F2C002B1A558E00D8ADDC /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
DA5753672B33E88A00FAC277 /* HotlineFileClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlineFileClient.swift; sourceTree = "<group>"; };
DA5753692B34A60D00FAC277 /* FileImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileImageView.swift; sourceTree = "<group>"; };
DA57536B2B36BA1D00FAC277 /* TextDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextDocument.swift; sourceTree = "<group>"; };
DA6300962B24036B0034CBFD /* HotlineClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlineClient.swift; sourceTree = "<group>"; };
DA77253E2B21176D006C5ABB /* NewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsView.swift; sourceTree = "<group>"; };
Expand All @@ -77,6 +77,8 @@
DA9CAFC32B126D5800CDA197 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
DA9CAFCA2B126E3300CDA197 /* HotlineTrackerClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlineTrackerClient.swift; sourceTree = "<group>"; };
DAAEE66A2B3FBC2100A5BA07 /* TransferInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferInfo.swift; sourceTree = "<group>"; };
DAAEE66C2B475F1400A5BA07 /* PreviewFileInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewFileInfo.swift; sourceTree = "<group>"; };
DAAEE66E2B47625600A5BA07 /* FilePreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewView.swift; sourceTree = "<group>"; };
DABFCC282B1530DC009F40D2 /* FoundationExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoundationExtensions.swift; sourceTree = "<group>"; };
DAC002182B21630900A6C290 /* SwiftUIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIExtensions.swift; sourceTree = "<group>"; };
DADDB28A2B22B31F0024040D /* Tracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tracker.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -196,6 +198,7 @@
DA2863DC2B3E8B7000A7D050 /* FilePreview.swift */,
DA32CD4C2B2931B50053B98B /* ChatMessage.swift */,
DA32CD4E2B2931CC0053B98B /* NewsInfo.swift */,
DAAEE66C2B475F1400A5BA07 /* PreviewFileInfo.swift */,
);
path = Models;
sourceTree = "<group>";
Expand All @@ -210,7 +213,7 @@
DAE735022B30C0BB000C56F6 /* MessageView.swift */,
DAE734FC2B2E65E9000C56F6 /* MessageBoardView.swift */,
DAE735002B2E71F2000C56F6 /* FilesView.swift */,
DA5753692B34A60D00FAC277 /* FileImageView.swift */,
DAAEE66E2B47625600A5BA07 /* FilePreviewView.swift */,
DA2863D72B37AD1C00A7D050 /* SettingsView.swift */,
);
path = macOS;
Expand Down Expand Up @@ -300,7 +303,6 @@
DADDB28D2B22B5920024040D /* Server.swift in Sources */,
DAE734FB2B2E41F9000C56F6 /* TrackerView.swift in Sources */,
DA9CAFBD2B126D5700CDA197 /* TrackerView.swift in Sources */,
DA57536A2B34A60D00FAC277 /* FileImageView.swift in Sources */,
DAE735032B30C0BB000C56F6 /* MessageView.swift in Sources */,
DA32CD4B2B29318E0053B98B /* FileInfo.swift in Sources */,
DA9CAFCB2B126E3300CDA197 /* HotlineTrackerClient.swift in Sources */,
Expand All @@ -316,6 +318,8 @@
DA77253F2B21176D006C5ABB /* NewsView.swift in Sources */,
DA4F2BF82B16A17200D8ADDC /* HotlineProtocol.swift in Sources */,
DA5753682B33E88A00FAC277 /* HotlineFileClient.swift in Sources */,
DAAEE66F2B47625600A5BA07 /* FilePreviewView.swift in Sources */,
DAAEE66D2B475F1400A5BA07 /* PreviewFileInfo.swift in Sources */,
DA0D698D2B1E7CF700C71DF5 /* UsersView.swift in Sources */,
DAE735052B3218D8000C56F6 /* NewsView.swift in Sources */,
DAE734F92B2E4185000C56F6 /* ServerView.swift in Sources */,
Expand Down
16 changes: 15 additions & 1 deletion Hotline/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,22 @@ struct Application: App {
.environment(model)
}
#elseif os(macOS)
// MARK: Tracker Window
Window("Servers", id: "servers") {
TrackerView()
.frame(minWidth: 250, minHeight: 250)
}
.keyboardShortcut(.init(.init("R"), modifiers: .command))
.defaultSize(width: 700, height: 550)
.defaultPosition(.center)


// MARK: Server Window
WindowGroup(id: "server", for: Server.self) { $server in
ServerView(server: $server)
.frame(minWidth: 400, minHeight: 300)
.environment(preferences)
} defaultValue: {
Server(name: nil, description: nil, address: "")
}
.defaultSize(width: 750, height: 700)
.defaultPosition(.center)
Expand All @@ -43,6 +48,15 @@ struct Application: App {
}
}

// WindowGroup(id: "preview", for: PreviewFileInfo.self) { info in
// FilePreviewView(info: info)
// .frame(minWidth: 400, minHeight: 300)
// }
// .defaultSize(width: 750, height: 700)
// .windowStyle(.hiddenTitleBar)
// .aspectRatio(nil, contentMode: .fit)

// MARK: Settings Window
Settings {
SettingsView()
.environment(preferences)
Expand Down
6 changes: 3 additions & 3 deletions Hotline/Hotline/HotlineClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class HotlineClient {

// MARK: -

func login(_ address: String, port: UInt16, login: String, password: String, username: String, iconID: UInt16, callback: ((HotlineTransactionError?, String?, UInt16?) -> Void)?) {
func login(_ address: String, port: UInt16, login: String?, password: String?, username: String, iconID: UInt16, callback: ((HotlineTransactionError?, String?, UInt16?) -> Void)?) {
print("AWAITING CONNECT")
self.connect(address: address, port: port) { [weak self] success in
guard success else {
Expand All @@ -90,8 +90,8 @@ class HotlineClient {
return
}

print("AWAITING LOGIN")
self?.sendLogin(login: login, password: password, username: username, iconID: iconID) { err, serverName, serverVersion in
print("AWAITING LOGIN \(login ?? "empty login") \(password ?? "empty pass")")
self?.sendLogin(login: login ?? "", password: password ?? "", username: username, iconID: iconID) { err, serverName, serverVersion in
// guard err == nil else {
// DispatchQueue.main.async {
// callback?(err, nil, nil)
Expand Down
5 changes: 1 addition & 4 deletions Hotline/Hotline/HotlineFileClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -533,12 +533,9 @@ class HotlineFileClient {
if let newData = data, !newData.isEmpty {
self.fileBytesDownloaded += newData.count
self.fileBytes.append(newData)
self.fileProgress?.completedUnitCount = Int64(self.fileBytesDownloaded)
self.status = .progress(Double(self.fileBytesDownloaded) / Double(self.referenceDataSize))



print("DOWNLOAD PROGRESS", self.fileProgress.debugDescription)
print("DOWNLOAD PROGRESS", self.fileBytesDownloaded, self.referenceDataSize, isComplete)
}

if self.fileBytesDownloaded < Int(self.referenceDataSize) {
Expand Down
11 changes: 11 additions & 0 deletions Hotline/Models/FileInfo.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SwiftUI
import UniformTypeIdentifiers

@Observable class FileInfo: Identifiable, Hashable {
let id: UUID
Expand All @@ -15,6 +16,16 @@ import SwiftUI
var expanded: Bool = false
var children: [FileInfo]? = nil

var isImage: Bool {
let fileExtension = (self.name as NSString).pathExtension
if let fileType = UTType(filenameExtension: fileExtension) {
if fileType.isSubtype(of: .image) {
return true
}
}
return false
}

init(hotlineFile: HotlineFile) {
self.id = UUID()
self.path = hotlineFile.path
Expand Down
110 changes: 71 additions & 39 deletions Hotline/Models/FilePreview.swift
Original file line number Diff line number Diff line change
@@ -1,41 +1,73 @@
import SwiftUI
import UniformTypeIdentifiers

//struct FilePreview: Identifiable, Hashable {
// let id: UUID
//
// let path: [String]
// let name: String
//
// let type: String
// let creator: String
// let fileSize: UInt
//
// let isFolder: Bool
// let isUnavailable: Bool
// var expanded: Bool = false
// var children: [FileInfo]? = nil
//
// init(hotlineFile: HotlineFile) {
// self.id = UUID()
// self.path = hotlineFile.path
// self.name = hotlineFile.name
// self.type = hotlineFile.type
// self.creator = hotlineFile.creator
// self.fileSize = UInt(hotlineFile.fileSize)
// self.isFolder = hotlineFile.isFolder
// self.isUnavailable = (!self.isFolder && (self.fileSize == 0))
//
// print(self.name, self.type, self.creator, self.isUnavailable)
// if self.isFolder {
// self.children = []
// }
// }
//
// static func == (lhs: FileInfo, rhs: FileInfo) -> Bool {
// return lhs.id == rhs.id
// }
//
// func hash(into hasher: inout Hasher) {
// hasher.combine(self.id)
// }
//}
enum FilePreviewState: Equatable {
case unloaded
case loading
case loaded
case failed
}

@Observable
final class FilePreview: HotlineFileClientDelegate {
@ObservationIgnored let info: PreviewFileInfo
@ObservationIgnored var client: HotlineFileClient? = nil

var state: FilePreviewState = .unloaded
var progress: Double = 0.0

#if os(macOS)
var image: NSImage?
#endif

init(info: PreviewFileInfo) {
self.info = info

self.client = HotlineFileClient(address: info.address, port: UInt16(info.port), reference: info.id, size: UInt32(info.size), type: .preview)
self.client?.delegate = self
}

func download() {
self.client?.downloadToMemory()
}

func cancel() {
self.client?.cancel(deleteIncompleteFile: true)
}

func hotlineFileStatusChanged(client: HotlineFileClient, reference: UInt32, status: HotlineFileClientStatus, timeRemaining: TimeInterval) {
print("FILE STATUS CHANGED", status)

switch status {
case .unconnected:
state = .unloaded
progress = 0.0
case .connecting:
state = .loading
progress = 0.0
case .connected:
state = .loading
progress = 0.0
case .progress(let p):
state = .loading
progress = p
case .failed(_):
state = .failed
progress = 0.0
case .completed:
state = .loaded
progress = 1.0
}
}

func hotlineFileDownloadedData(client: HotlineFileClient, reference: UInt32, data: Data) {
self.state = .loaded

let fileExtension = (info.name as NSString).pathExtension
if let fileType = UTType(filenameExtension: fileExtension) {
if fileType.isSubtype(of: .image) {
self.image = NSImage(data: data)
}
}
}
}
Loading

0 comments on commit 2f8f71d

Please sign in to comment.