From 82f5b049e70d7d644728bcfc939e06db7b4df19a Mon Sep 17 00:00:00 2001 From: Nindi Gill Date: Sun, 11 Feb 2024 17:20:38 +1100 Subject: [PATCH] Add copy to clipboard for Firmwares (#119) --- Mist.xcodeproj/project.pbxproj | 4 ++ Mist/Views/Components/FloatingAlert.swift | 38 +++++++++++++++++++ Mist/Views/ContentView.swift | 45 ++++++++++++++++------- Mist/Views/List/ListRowFirmware.swift | 34 +++++++++++++---- Mist/Views/List/ListRowInstaller.swift | 4 +- 5 files changed, 102 insertions(+), 23 deletions(-) create mode 100644 Mist/Views/Components/FloatingAlert.swift diff --git a/Mist.xcodeproj/project.pbxproj b/Mist.xcodeproj/project.pbxproj index 00345cf..144e58c 100644 --- a/Mist.xcodeproj/project.pbxproj +++ b/Mist.xcodeproj/project.pbxproj @@ -93,6 +93,7 @@ 398734D028603D9E00B4C357 /* UInt8+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398734CF28603D9E00B4C357 /* UInt8+Extension.swift */; }; 398734D228603DE700B4C357 /* [UInt8]+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398734D128603DE700B4C357 /* [UInt8]+Extension.swift */; }; 398734D4286046B000B4C357 /* UInt32+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398734D3286046B000B4C357 /* UInt32+Extension.swift */; }; + 398BE6B52B62450500FE0C29 /* FloatingAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398BE6B42B62450500FE0C29 /* FloatingAlert.swift */; }; 39CA25E32941D8BB0030711E /* FileAttributesUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CA25E22941D8BB0030711E /* FileAttributesUpdater.swift */; }; 39CB5E3D293F5C2E00CFDBB8 /* Catalog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CB5E3C293F5C2E00CFDBB8 /* Catalog.swift */; }; 39CB5E3F2941486D00CFDBB8 /* CatalogSeedType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CB5E3E2941486D00CFDBB8 /* CatalogSeedType.swift */; }; @@ -250,6 +251,7 @@ 398734CF28603D9E00B4C357 /* UInt8+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt8+Extension.swift"; sourceTree = ""; }; 398734D128603DE700B4C357 /* [UInt8]+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "[UInt8]+Extension.swift"; sourceTree = ""; }; 398734D3286046B000B4C357 /* UInt32+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt32+Extension.swift"; sourceTree = ""; }; + 398BE6B42B62450500FE0C29 /* FloatingAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingAlert.swift; sourceTree = ""; }; 39CA25E22941D8BB0030711E /* FileAttributesUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileAttributesUpdater.swift; sourceTree = ""; }; 39CB5E3C293F5C2E00CFDBB8 /* Catalog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Catalog.swift; sourceTree = ""; }; 39CB5E3E2941486D00CFDBB8 /* CatalogSeedType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CatalogSeedType.swift; sourceTree = ""; }; @@ -538,6 +540,7 @@ children = ( 39252AA2285C3CC400956C74 /* CodesigningPickerView.swift */, 39252AA4285C463A00956C74 /* DynamicTextView.swift */, + 398BE6B42B62450500FE0C29 /* FloatingAlert.swift */, 39252A86285ACE9C00956C74 /* FooterText.swift */, 5795700C2A31B081004C7051 /* MistActionButtonStyle.swift */, 39252AA0285C2A1600956C74 /* PaddedDivider.swift */, @@ -843,6 +846,7 @@ 39FF05F62859850F00A86670 /* SettingsFirmwaresView.swift in Sources */, 3935F4A6286AD3E100760AB0 /* ActivityHeaderView.swift in Sources */, 3935F480286551FB00760AB0 /* Double+Extension.swift in Sources */, + 398BE6B52B62450500FE0C29 /* FloatingAlert.swift in Sources */, 39252ABD285C8FFC00956C74 /* ListRowInstaller.swift in Sources */, 3935F49D286ABE4D00760AB0 /* FooterView.swift in Sources */, 390451CC2856F23100E0B563 /* ScaledSystemImage.swift in Sources */, diff --git a/Mist/Views/Components/FloatingAlert.swift b/Mist/Views/Components/FloatingAlert.swift new file mode 100644 index 0000000..c9cf7a0 --- /dev/null +++ b/Mist/Views/Components/FloatingAlert.swift @@ -0,0 +1,38 @@ +// +// FloatingAlert.swift +// Mist +// +// Created by Nindi Gill on 25/1/2024. +// + +import Foundation +import SwiftUI + +struct FloatingAlert: View { + var image: String + var message: String + private let length: CGFloat = 200 + private let imageLength: CGFloat = 160 + private let cornerRadius: CGFloat = 20 + + var body: some View { + VStack { + Image(systemName: image) + .resizable() + .scaledToFit() + Text(message) + .font(.title) + .fontWeight(.medium) + } + .foregroundStyle(.secondary) + .padding() + .frame(width: length, height: length) + .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: cornerRadius)) + } +} + +struct FloatingAlert_Previews: PreviewProvider { + static var previews: some View { + FloatingAlert(image: "list.bullet.clipboard.fill", message: "Copied to Clipboard") + } +} diff --git a/Mist/Views/ContentView.swift b/Mist/Views/ContentView.swift index 2152250..af6f44a 100644 --- a/Mist/Views/ContentView.swift +++ b/Mist/Views/ContentView.swift @@ -21,6 +21,7 @@ struct ContentView: View { @State private var searchString: String = "" @State private var openPanel: NSOpenPanel = .init() @State private var savePanel: NSSavePanel = .init() + @State private var copiedToClipboard: Bool = false @StateObject private var taskManager: TaskManager = .shared private var filteredFirmwares: [Firmware] { var filteredFirmwares: [Firmware] = firmwares @@ -74,29 +75,35 @@ struct ContentView: View { private let height: CGFloat = 720 var body: some View { + // swiftlint:disable:next closure_body_length VStack(spacing: 0) { HeaderView(downloadType: $downloadType) Divider() if downloadType == .firmware && filteredFirmwares.isEmpty || downloadType == .installer && filteredInstallers.isEmpty { EmptyCollectionView("No macOS \(downloadType.description)s found!\n\nಥ_ಥ") } else { - List { - ForEach(releaseNames(for: downloadType), id: \.self) { releaseName in - Section(header: Text(releaseName)) { - switch downloadType { - case .firmware: - ForEach(filteredFirmwares(for: releaseName)) { firmware in - ListRowFirmware(firmware: firmware, savePanel: $savePanel, tasksInProgress: $tasksInProgress, taskManager: taskManager) - .tag(firmware) - } - case .installer: - ForEach(filteredInstallers(for: releaseName)) { installer in - ListRowInstaller(installer: installer, openPanel: $openPanel, tasksInProgress: $tasksInProgress, taskManager: taskManager) - .tag(installer) + ZStack { + List { + ForEach(releaseNames(for: downloadType), id: \.self) { releaseName in + Section(header: Text(releaseName)) { + switch downloadType { + case .firmware: + ForEach(filteredFirmwares(for: releaseName)) { firmware in + ListRowFirmware(firmware: firmware, savePanel: $savePanel, copiedToClipboard: $copiedToClipboard, tasksInProgress: $tasksInProgress, taskManager: taskManager) + .tag(firmware) + } + case .installer: + ForEach(filteredInstallers(for: releaseName)) { installer in + ListRowInstaller(installer: installer, openPanel: $openPanel, tasksInProgress: $tasksInProgress, taskManager: taskManager) + .tag(installer) + } } } } } + if copiedToClipboard { + FloatingAlert(image: "list.bullet.clipboard.fill", message: "Copied to Clipboard") + } } } Divider() @@ -119,6 +126,18 @@ struct ContentView: View { .onAppear { refresh() } + .onChange(of: copiedToClipboard) { copied in + + guard copied else { + return + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + withAnimation { + copiedToClipboard = false + } + } + } } private func refresh() { diff --git a/Mist/Views/List/ListRowFirmware.swift b/Mist/Views/List/ListRowFirmware.swift index 405dc36..4f523ff 100644 --- a/Mist/Views/List/ListRowFirmware.swift +++ b/Mist/Views/List/ListRowFirmware.swift @@ -17,6 +17,7 @@ struct ListRowFirmware: View { private var retryDelay: Int = 30 var firmware: Firmware @Binding var savePanel: NSSavePanel + @Binding var copiedToClipboard: Bool @Binding var tasksInProgress: Bool @ObservedObject var taskManager: TaskManager @State private var alertType: FirmwareAlertType = .compatibility @@ -54,14 +55,23 @@ struct ListRowFirmware: View { size: firmware.size.bytesString(), tooltip: firmware.tooltip ) - Button { - firmware.compatible ? validate() : showCompatibilityWarning() - } label: { - Image(systemName: "arrow.down.circle") - .font(.body.bold()) + HStack(spacing: 1) { + Button { + firmware.compatible ? validate() : showCompatibilityWarning() + } label: { + Image(systemName: "arrow.down.circle") + .padding(.vertical, 1.5) + } + .help("Download macOS Firmware") + .buttonStyle(.mistAction) + Button { + copyToClipboard() + } label: { + Image(systemName: "list.bullet.clipboard") + } + .help("Copy macOS Firmware URL to Clipboard") + .buttonStyle(.mistAction) } - .help("Download macOS Firmware") - .buttonStyle(.mistAction) .clipShape(Capsule()) } .alert(isPresented: $showAlert) { @@ -108,6 +118,14 @@ struct ListRowFirmware: View { } } + private func copyToClipboard() { + NSPasteboard.general.declareTypes([.string], owner: nil) + NSPasteboard.general.setString(firmware.url, forType: .string) + withAnimation(.easeIn) { + copiedToClipboard = true + } + } + private func save() { showSavePanel = false savePanel.title = "Download Firmware" @@ -157,6 +175,6 @@ struct ListRowFirmware: View { struct ListRowFirmware_Previews: PreviewProvider { static var previews: some View { - ListRowFirmware(firmware: .example, savePanel: .constant(NSSavePanel()), tasksInProgress: .constant(false), taskManager: .shared) + ListRowFirmware(firmware: .example, savePanel: .constant(NSSavePanel()), copiedToClipboard: .constant(false), tasksInProgress: .constant(false), taskManager: .shared) } } diff --git a/Mist/Views/List/ListRowInstaller.swift b/Mist/Views/List/ListRowInstaller.swift index ed93509..e0a28af 100644 --- a/Mist/Views/List/ListRowInstaller.swift +++ b/Mist/Views/List/ListRowInstaller.swift @@ -88,7 +88,7 @@ struct ListRowInstaller: View { Button { pressButton(.download) } label: { - Image(systemName: "arrow.down.circle").font(.body.bold()) + Image(systemName: "arrow.down.circle") } .help("Download and export macOS Installer") .buttonStyle(.mistAction) @@ -98,7 +98,7 @@ struct ListRowInstaller: View { Button { pressButton(.volumeSelection) } label: { - Image(systemName: "externaldrive").font(.body.bold()) + Image(systemName: "externaldrive") .padding(.vertical, 1) } .help("Create bootable macOS Installer")