aboutsummaryrefslogtreecommitdiff
path: root/Hotline/macOS
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2025-11-11 15:39:02 -0800
committerDustin Mierau <dustin@mierau.me>2025-11-11 15:39:02 -0800
commitf87ff05d17cedf21845abbb8d750759ba725a263 (patch)
tree154feff5eed1b3f7787eeb2f743ffe6602b6cd31 /Hotline/macOS
parentcdaff85ac22f45e39ab1f02474beaa7aada9ad9b (diff)
Folder download logic cleanup. Display folder icon on folder download transfers with small icon for the current file. Fix progress for folder downloads.
Diffstat (limited to 'Hotline/macOS')
-rw-r--r--Hotline/macOS/TransfersView.swift224
1 files changed, 133 insertions, 91 deletions
diff --git a/Hotline/macOS/TransfersView.swift b/Hotline/macOS/TransfersView.swift
index bf8c8bd..5f9357d 100644
--- a/Hotline/macOS/TransfersView.swift
+++ b/Hotline/macOS/TransfersView.swift
@@ -70,58 +70,70 @@ struct TransfersView: View {
.listStyle(.inset)
.environment(\.defaultMinListRowHeight, 56)
.contextMenu(forSelectionType: TransferInfo.self) { items in
- if items.allSatisfy(\.completed) {
- let fileURLs: [URL] = items.compactMap(\.fileURL)
-
- Button("Remove Transfer\(items.count > 1 ? "s" : "")", systemImage: "xmark") {
- self.appState.cancelTransfers(ids: items.map(\.id))
- self.selectedTransfers = []
- }
-
- Divider()
-
- Button("Open", systemImage: "arrow.up.right.square") {
- for fileURL in fileURLs {
- NSWorkspace.shared.open(fileURL)
- }
- }
-
- self.openWithMenu(for: fileURLs)
-
- Button("Show in Finder", systemImage: "finder") {
- NSWorkspace.shared.activateFileViewerSelecting(fileURLs)
+ self.contextMenuForItems(items)
+ } primaryAction: { items in
+ self.performPrimaryAction(for: items)
+ }
+ }
+
+ // MARK: - Double Click
+
+ private func performPrimaryAction(for items: Set<TransferInfo>) {
+ if let fileURL = items.first?.fileURL {
+ NSWorkspace.shared.open(fileURL)
+ }
+ }
+
+ // MARK: - Context Menu
+
+ @ViewBuilder
+ private func contextMenuForItems(_ items: Set<TransferInfo>) -> some View {
+ if items.allSatisfy(\.completed) {
+ let fileURLs: [URL] = items.compactMap(\.fileURL)
+
+ Button("Remove Transfer\(items.count > 1 ? "s" : "")", systemImage: "xmark") {
+ self.appState.cancelTransfers(ids: items.map(\.id))
+ self.selectedTransfers = []
+ }
+
+ Divider()
+
+ Button("Open", systemImage: "arrow.up.right.square") {
+ for fileURL in fileURLs {
+ NSWorkspace.shared.open(fileURL)
}
+ }
+
+ self.openWithMenu(for: fileURLs)
+
+ Button("Show in Finder", systemImage: "finder") {
+ NSWorkspace.shared.activateFileViewerSelecting(fileURLs)
+ }
+
+ Divider()
+
+ Button("Move to Trash", systemImage: "trash") {
+ self.appState.cancelTransfers(ids: items.map(\.id))
+ NSWorkspace.shared.recycle(fileURLs)
+ self.selectedTransfers = []
+ }
+ } else {
+ Button("Remove Transfer\(items.count > 1 ? "s" : "")", systemImage: "xmark") {
+ self.appState.cancelTransfers(ids: items.map(\.id))
+ self.selectedTransfers = []
+ }
+
+ Divider()
+
+ Button("Move to Trash", systemImage: "trash") {
+ self.appState.cancelTransfers(ids: items.map(\.id))
- Divider()
-
- Button("Move to Trash", systemImage: "trash") {
- self.appState.cancelTransfers(ids: items.map(\.id))
+ let fileURLs: [URL] = items.compactMap(\.fileURL)
+ if !fileURLs.isEmpty {
NSWorkspace.shared.recycle(fileURLs)
- self.selectedTransfers = []
}
- }
- else {
- Button("Remove Transfer\(items.count > 1 ? "s" : "")", systemImage: "xmark") {
- self.appState.cancelTransfers(ids: items.map(\.id))
- self.selectedTransfers = []
- }
-
- Divider()
- Button("Move to Trash", systemImage: "trash") {
- self.appState.cancelTransfers(ids: items.map(\.id))
-
- let fileURLs: [URL] = items.compactMap(\.fileURL)
- if !fileURLs.isEmpty {
- NSWorkspace.shared.recycle(fileURLs)
- }
-
- self.selectedTransfers = []
- }
- }
- } primaryAction: { items in
- if let fileURL = items.first?.fileURL {
- NSWorkspace.shared.open(fileURL)
+ self.selectedTransfers = []
}
}
}
@@ -250,6 +262,7 @@ struct TransfersView: View {
}
}
}
+
}
// MARK: - Transfer Row
@@ -259,6 +272,56 @@ struct TransferRow: View {
@Bindable var transfer: TransferInfo
+ var body: some View {
+ HStack(alignment: .center, spacing: 8) {
+ if self.transfer.isFolder {
+ self.folderIconView
+ }
+ else {
+ self.fileIconView
+ }
+
+ VStack(alignment: .leading, spacing: 2) {
+ HStack(alignment: .firstTextBaseline, spacing: 8) {
+ Text(self.transfer.title)
+ .font(.headline)
+ .lineLimit(1)
+ .truncationMode(.tail)
+
+ Spacer()
+
+ if !self.transfer.done {
+ self.statsView
+ }
+ }
+
+ // Progress bar and status
+ if self.transfer.cancelled {
+ Text("Cancelled")
+ .font(.subheadline)
+ .foregroundStyle(.secondary)
+ }
+ else if self.transfer.failed {
+ Text("Failed")
+ .font(.subheadline)
+ .foregroundStyle(.secondary)
+ }
+ else if self.transfer.completed {
+ Text("Downloaded")
+ .font(.subheadline)
+ .foregroundStyle(.fileComplete)
+ }
+ else {
+ ProgressView(value: self.transfer.progress, total: 1.0)
+ .progressViewStyle(.linear)
+ .controlSize(.large)
+ }
+ }
+ }
+ }
+
+ // MARK: -
+
private var statsView: some View {
HStack(spacing: 8) {
// Progress percentage
@@ -266,28 +329,24 @@ struct TransferRow: View {
// Speed
if let speed = self.transfer.speed {
- // TODO: Use arrow.up for uploads.
- Label(self.formatSpeed(speed), systemImage: "arrow.down")
-// Text(self.formatSpeed(speed))
+ Label(self.formatSpeed(speed), systemImage: self.transfer.isUpload ? "arrow.up" : "arrow.down")
}
// Time remaining
if let timeRemaining = self.transfer.timeRemaining {
Label(self.formatTimeRemaining(timeRemaining), systemImage: "clock")
-// Text(self.formatTimeRemaining(timeRemaining))
}
// File size
Label(self.formatSize(self.transfer.size), systemImage: "document")
-// Text(self.formatSize(self.transfer.size))
}
.font(.subheadline)
.foregroundStyle(.secondary)
.monospacedDigit()
}
- private var fileIconView: some View {
- FileIconView(filename: self.transfer.title, fileType: nil)
+ private var folderIconView: some View {
+ FolderIconView()
.frame(width: 32, height: 32)
.overlay(alignment: .bottomTrailing) {
if self.transfer.cancelled || self.transfer.failed {
@@ -305,50 +364,33 @@ struct TransferRow: View {
.scaledToFit()
.frame(width: 16, height: 16)
}
+ else {
+ FileIconView(filename: self.transfer.title, fileType: nil)
+ .frame(width: 16, height: 16)
+ }
}
}
- var body: some View {
- HStack(alignment: .center, spacing: 8) {
- self.fileIconView
-
- VStack(alignment: .leading, spacing: 2) {
- HStack(alignment: .firstTextBaseline, spacing: 8) {
- Text(self.transfer.title)
- .font(.headline)
- .lineLimit(1)
- .truncationMode(.tail)
-
- Spacer()
-
- if !self.transfer.done {
- self.statsView
- }
- }
-
- // Progress bar and status
- if self.transfer.cancelled {
- Text("Cancelled")
- .font(.subheadline)
- .foregroundStyle(.secondary)
- }
- else if self.transfer.failed {
- Text("Failed")
- .font(.subheadline)
- .foregroundStyle(.secondary)
+ private var fileIconView: some View {
+ FileIconView(filename: self.transfer.title, fileType: nil)
+ .frame(width: 32, height: 32)
+ .overlay(alignment: .bottomTrailing) {
+ if self.transfer.cancelled || self.transfer.failed {
+ Image(systemName: "exclamationmark.triangle.fill")
+ .resizable()
+ .symbolRenderingMode(.multicolor)
+ .scaledToFit()
+ .frame(width: 16, height: 16)
}
else if self.transfer.completed {
- Text("Downloaded")
- .font(.subheadline)
- .foregroundStyle(.fileComplete)
- }
- else {
- ProgressView(value: self.transfer.progress, total: 1.0)
- .progressViewStyle(.linear)
- .controlSize(.large)
+ Image(systemName: "checkmark.circle.fill")
+ .resizable()
+ .symbolRenderingMode(.palette)
+ .foregroundStyle(.white, .fileComplete)
+ .scaledToFit()
+ .frame(width: 16, height: 16)
}
}
- }
}
// MARK: - Formatting