aboutsummaryrefslogtreecommitdiff
path: root/Hotline
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2025-11-11 16:13:47 -0800
committerDustin Mierau <dustin@mierau.me>2025-11-11 16:13:47 -0800
commit86560ac84504f2da63c8fb08505857d8827684d4 (patch)
tree92fdd4ac23fcd6b0020ab58e1e9da9750a287b98 /Hotline
parentf87ff05d17cedf21845abbb8d750759ba725a263 (diff)
Use linear progress bar for transfers in the Server side bar.
Diffstat (limited to 'Hotline')
-rw-r--r--Hotline/Models/TransferInfo.swift43
-rw-r--r--Hotline/macOS/ServerView.swift92
-rw-r--r--Hotline/macOS/TransfersView.swift54
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