diff options
| author | Dustin Mierau <dustin@mierau.me> | 2025-11-11 16:13:47 -0800 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2025-11-11 16:13:47 -0800 |
| commit | 86560ac84504f2da63c8fb08505857d8827684d4 (patch) | |
| tree | 92fdd4ac23fcd6b0020ab58e1e9da9750a287b98 /Hotline | |
| parent | f87ff05d17cedf21845abbb8d750759ba725a263 (diff) | |
Use linear progress bar for transfers in the Server side bar.
Diffstat (limited to 'Hotline')
| -rw-r--r-- | Hotline/Models/TransferInfo.swift | 43 | ||||
| -rw-r--r-- | Hotline/macOS/ServerView.swift | 92 | ||||
| -rw-r--r-- | Hotline/macOS/TransfersView.swift | 54 |
3 files changed, 111 insertions, 78 deletions
diff --git a/Hotline/Models/TransferInfo.swift b/Hotline/Models/TransferInfo.swift index ebe9f64..cac4b90 100644 --- a/Hotline/Models/TransferInfo.swift +++ b/Hotline/Models/TransferInfo.swift @@ -58,4 +58,47 @@ class TransferInfo: Identifiable, Equatable, Hashable { func hash(into hasher: inout Hasher) { hasher.combine(self.id) } + +// var formatSize(_ bytes: UInt) -> String { +// let formatter = ByteCountFormatter() +// formatter.countStyle = .file +// formatter.allowedUnits = [.useKB, .useMB, .useGB] +// return formatter.string(fromByteCount: Int64(bytes)) +// } + + var displaySize: String { + let formatter = ByteCountFormatter() + formatter.countStyle = .file + formatter.allowedUnits = [.useKB, .useMB, .useGB] + return formatter.string(fromByteCount: Int64(self.size)) + } + + var displaySpeed: String? { + guard let speed = self.speed else { + return nil + } + + let formatter = ByteCountFormatter() + formatter.countStyle = .file + formatter.allowedUnits = [.useKB, .useMB, .useGB] + return "\(formatter.string(fromByteCount: Int64(speed)))/s" + } + + var displayTimeRemaining: String? { + guard let timeRemaining = self.timeRemaining else { + return nil + } + + if timeRemaining < 60 { + return "\(Int(timeRemaining))s" + } else if timeRemaining < 3600 { + let minutes = Int(timeRemaining / 60) + let secs = Int(timeRemaining.truncatingRemainder(dividingBy: 60)) + return "\(minutes)m \(secs)s" + } else { + let hours = Int(timeRemaining / 3600) + let minutes = Int((timeRemaining.truncatingRemainder(dividingBy: 3600)) / 60) + return "\(hours)h \(minutes)m" + } + } } diff --git a/Hotline/macOS/ServerView.swift b/Hotline/macOS/ServerView.swift index d4b3407..98ab976 100644 --- a/Hotline/macOS/ServerView.swift +++ b/Hotline/macOS/ServerView.swift @@ -107,13 +107,10 @@ struct ServerView: View { if self.model.status == .disconnected { VStack(alignment: .center) { Spacer() -// if self.connectionDisplayed { - self.connectForm -// } + self.connectForm Spacer() } .navigationTitle("Connect to Server") -// .animation(.default.delay(0.25), value: self.connectionDisplayed) } else if self.model.status.isLoggingIn { HStack { @@ -281,7 +278,7 @@ struct ServerView: View { var transfersSection: some View { ForEach(model.transfers) { transfer in - TransferItemView(transfer: transfer) + ServerTransferRow(transfer: transfer) } } @@ -421,7 +418,7 @@ struct ServerView: View { } -struct TransferItemView: View { +struct ServerTransferRow: View { let transfer: TransferInfo @Environment(\.controlActiveState) private var controlActiveState @@ -429,29 +426,11 @@ struct TransferItemView: View { @State private var hovered: Bool = false @State private var buttonHovered: Bool = false - private func formattedProgressHelp() -> String { - if self.transfer.completed { - return "File transfer complete" - } - else if self.transfer.failed { - return "File transfer failed" - } - else if self.transfer.progress > 0.0 { - if let estimate = self.transfer.timeRemaining, estimate > 0.0 { - return "\(round(self.transfer.progress * 100.0))% – \(estimate) seconds left" - } - else { - return "\(round(self.transfer.progress * 100.0))% complete" - } - } - return "" - } - var body: some View { HStack(alignment: .center, spacing: 5) { HStack(spacing: 0) { Spacer() - if transfer.isFolder { + if self.transfer.isFolder { Image("Folder") .resizable() .scaledToFit() @@ -467,11 +446,26 @@ struct TransferItemView: View { } .frame(width: 20) - Text(transfer.title) + Text(self.transfer.folderName ?? self.transfer.title) .lineLimit(1) .truncationMode(.middle) - Spacer() + Spacer(minLength: 0) + + if !self.transfer.done { + if self.transfer.progress == 0.0 { + ProgressView() + .progressViewStyle(.linear) + .controlSize(.extraLarge) + .frame(maxWidth: 40) + } + else { + ProgressView(value: self.transfer.progress, total: 1.0) + .progressViewStyle(.linear) + .controlSize(.extraLarge) + .frame(maxWidth: 40) + } + } if self.hovered { Button { @@ -509,19 +503,9 @@ struct TransferItemView: View { .frame(width: 16, height: 16) .opacity(controlActiveState == .inactive ? 0.5 : 1.0) } - else if transfer.progress == 0.0 { - ProgressView() - .progressViewStyle(.circular) - .controlSize(.small) - } - else { - ProgressView(value: transfer.progress, total: 1.0) - .progressViewStyle(.circular) - .controlSize(.small) - } } .onHover { hovered in - withAnimation(.easeOut(duration: 0.25)) { + withAnimation(.snappy(duration: 0.25, extraBounce: 0.3)) { self.hovered = hovered } } @@ -529,9 +513,37 @@ struct TransferItemView: View { guard transfer.completed, let url = transfer.fileURL else { return } - + NSWorkspace.shared.activateFileViewerSelecting([url]) } - .help(formattedProgressHelp()) + .help(self.formattedProgressHelp) + } + + private var formattedProgressHelp: String { + if self.transfer.completed { + return "File transfer complete" + } + else if self.transfer.failed { + return "File transfer failed" + } + else if self.transfer.cancelled { + return "File transfer cancelled" + } + else if self.transfer.progress > 0.0 { + var parts: [String] = [] + + if let speed = self.transfer.displaySpeed { + parts.append(speed) + } + + if let timeRemaining = self.transfer.displayTimeRemaining { + parts.append(timeRemaining) + } + + if parts.count > 0 { + return parts.joined(separator: " • ") + } + } + return "" } } diff --git a/Hotline/macOS/TransfersView.swift b/Hotline/macOS/TransfersView.swift index 5f9357d..9bef02c 100644 --- a/Hotline/macOS/TransfersView.swift +++ b/Hotline/macOS/TransfersView.swift @@ -312,9 +312,17 @@ struct TransferRow: View { .foregroundStyle(.fileComplete) } else { - ProgressView(value: self.transfer.progress, total: 1.0) - .progressViewStyle(.linear) - .controlSize(.large) + if self.transfer.progress == 0 { + ProgressView() + .progressViewStyle(.linear) + .controlSize(.large) + } + else { + ProgressView(value: self.transfer.progress, total: 1.0) + .progressViewStyle(.linear) + .controlSize(.large) + } + } } } @@ -328,17 +336,17 @@ struct TransferRow: View { // Text("\(Int(self.transfer.progress * 100))%") // Speed - if let speed = self.transfer.speed { - Label(self.formatSpeed(speed), systemImage: self.transfer.isUpload ? "arrow.up" : "arrow.down") + if let speed = self.transfer.displaySpeed { + Label(speed, systemImage: self.transfer.isUpload ? "arrow.up" : "arrow.down") } // Time remaining - if let timeRemaining = self.transfer.timeRemaining { - Label(self.formatTimeRemaining(timeRemaining), systemImage: "clock") + if let timeRemaining = self.transfer.displayTimeRemaining { + Label(timeRemaining, systemImage: "clock") } // File size - Label(self.formatSize(self.transfer.size), systemImage: "document") + Label(self.transfer.displaySize, systemImage: "document") } .font(.subheadline) .foregroundStyle(.secondary) @@ -392,36 +400,6 @@ struct TransferRow: View { } } } - - // MARK: - Formatting - - private func formatSize(_ bytes: UInt) -> String { - let formatter = ByteCountFormatter() - formatter.countStyle = .file - formatter.allowedUnits = [.useKB, .useMB, .useGB] - return formatter.string(fromByteCount: Int64(bytes)) - } - - private func formatSpeed(_ bytesPerSecond: Double) -> String { - let formatter = ByteCountFormatter() - formatter.countStyle = .file - formatter.allowedUnits = [.useKB, .useMB, .useGB] - return "\(formatter.string(fromByteCount: Int64(bytesPerSecond)))/s" - } - - private func formatTimeRemaining(_ seconds: TimeInterval) -> String { - if seconds < 60 { - return "\(Int(seconds))s" - } else if seconds < 3600 { - let minutes = Int(seconds / 60) - let secs = Int(seconds.truncatingRemainder(dividingBy: 60)) - return "\(minutes)m \(secs)s" - } else { - let hours = Int(seconds / 3600) - let minutes = Int((seconds.truncatingRemainder(dividingBy: 3600)) / 60) - return "\(hours)h \(minutes)m" - } - } } // MARK: - Preview |