aboutsummaryrefslogtreecommitdiff
path: root/Hotline/Shared
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2024-01-08 14:45:58 -0800
committerDustin Mierau <dustin@mierau.me>2024-01-08 14:45:58 -0800
commite9bae0328801d5a056f33beb0f764c6d35e518a2 (patch)
treefcf853330f8de68948b089a5b15e2ef4c6dbb47c /Hotline/Shared
parent4349f50167f50b30d7cd71854882ab33ac6ea5ea (diff)
Move to a standard SwiftUI window for preview windows so I can add toolbar items from SwiftUI. Not ideal. Added support for previewing text files too.
Diffstat (limited to 'Hotline/Shared')
-rw-r--r--Hotline/Shared/AnimatedImageView.swift98
-rw-r--r--Hotline/Shared/DataAdditions.swift23
-rw-r--r--Hotline/Shared/FileIconView.swift42
-rw-r--r--Hotline/Shared/URLAdditions.swift42
4 files changed, 205 insertions, 0 deletions
diff --git a/Hotline/Shared/AnimatedImageView.swift b/Hotline/Shared/AnimatedImageView.swift
new file mode 100644
index 0000000..0d92715
--- /dev/null
+++ b/Hotline/Shared/AnimatedImageView.swift
@@ -0,0 +1,98 @@
+import SwiftUI
+
+struct AnimatedImageView: NSViewRepresentable {
+ var image: NSImage?
+
+ let minimumSize: CGSize = CGSize(width: 350, height: 350)
+ let presentationPaddingRatio: Double = 0.5
+
+ func makeNSView(context: Context) -> NSImageView {
+ let imageView = NSImageView()
+ imageView.imageScaling = .scaleProportionallyUpOrDown
+ imageView.animates = true
+ imageView.isEditable = false
+ imageView.allowsCutCopyPaste = true
+ imageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
+ imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
+
+ if let img = self.image {
+ imageView.image = img
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+ self.resizeWindowForImageView(imageView)
+ }
+ }
+
+ return imageView
+ }
+
+ func updateNSView(_ nsView: NSImageView, context: Context) {
+ nsView.image = self.image
+ }
+
+ // MARK: -
+
+ func resizeWindowForImageView(_ imageView: NSImageView) {
+ guard let window = imageView.window, let img = imageView.image else {
+ return
+ }
+
+ var windowRect = window.contentLayoutRect
+ let windowChromeSize = CGSize(width: window.frame.width - windowRect.width, height: window.frame.height - windowRect.height)
+
+ var windowMinSize: CGSize = windowRect.size
+ let centerPoint = CGPoint(x: window.frame.midX, y: window.frame.midY)
+
+ windowRect.size = img.size
+
+ if let screen = window.screen {
+ var paddedScreenSize = screen.frame.size
+ paddedScreenSize.width *= self.presentationPaddingRatio
+ paddedScreenSize.height *= self.presentationPaddingRatio
+
+ windowMinSize = aspectFit(source: windowRect.size, bounds: self.minimumSize)
+ windowRect.size = aspectFit(source: windowRect.size, bounds: paddedScreenSize, minimum: windowMinSize)
+ }
+
+ windowRect.size.width += windowChromeSize.width
+ windowRect.size.height += windowChromeSize.height
+
+ windowRect.origin.x = centerPoint.x - windowRect.width / 2.0
+ windowRect.origin.y = centerPoint.y - windowRect.height / 2.0
+
+ window.setFrame(windowRect, display: true, animate: true)
+
+// Do these APIs even work??
+// window.aspectRatio = windowRect.size
+// window.contentAspectRatio = windowRect.size
+ }
+
+ func aspectFit(source sourceSize: CGSize, bounds boundingSize: CGSize, minimum minSize: CGSize? = nil) -> CGSize {
+ let sourceAspectRatio = sourceSize.width / sourceSize.height
+
+ var fitSize: CGSize = sourceSize
+
+ if fitSize.width > boundingSize.width {
+ fitSize.width = boundingSize.width
+ fitSize.height = fitSize.width / sourceAspectRatio
+ }
+
+ if fitSize.height > boundingSize.height {
+ fitSize.height = boundingSize.height
+ fitSize.width = fitSize.height * sourceAspectRatio
+ }
+
+ if let m = minSize {
+ if fitSize.width < m.width {
+ fitSize.width = m.width
+ fitSize.height = fitSize.width / sourceAspectRatio
+ }
+
+ if fitSize.height < m.height {
+ fitSize.height = m.height
+ fitSize.width = fitSize.height * sourceAspectRatio
+ }
+ }
+
+ return fitSize
+ }
+}
diff --git a/Hotline/Shared/DataAdditions.swift b/Hotline/Shared/DataAdditions.swift
new file mode 100644
index 0000000..0e9dd96
--- /dev/null
+++ b/Hotline/Shared/DataAdditions.swift
@@ -0,0 +1,23 @@
+import Foundation
+
+extension Data {
+ func saveAsFileToDownloads(filename: String, bounceDock: Bool = true) -> Bool {
+ let folderURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
+ let filePath = folderURL.generateUniqueFilePath(filename: filename)
+ if FileManager.default.createFile(atPath: filePath, contents: nil) {
+ if let h = FileHandle(forWritingAtPath: filePath) {
+ try? h.write(contentsOf: self)
+ try? h.close()
+ if bounceDock {
+ #if os(macOS)
+ var downloadURL = URL(filePath: filePath)
+ downloadURL.resolveSymlinksInPath()
+ DistributedNotificationCenter.default().post(name: .init("com.apple.DownloadFileFinished"), object: downloadURL.path)
+ #endif
+ }
+ return true
+ }
+ }
+ return false
+ }
+}
diff --git a/Hotline/Shared/FileIconView.swift b/Hotline/Shared/FileIconView.swift
new file mode 100644
index 0000000..7336cf3
--- /dev/null
+++ b/Hotline/Shared/FileIconView.swift
@@ -0,0 +1,42 @@
+import SwiftUI
+import UniformTypeIdentifiers
+
+struct FileIconView: View {
+ let filename: String
+
+ #if os(iOS)
+ private func fileIcon(filename: String) -> Image {
+ let fileExtension = (filename as NSString).pathExtension
+ if let fileType = UTType(filenameExtension: fileExtension) {
+ if fileType.isSubtype(of: .movie) {
+ return Image(systemName: "play.rectangle")
+ }
+ else if fileType.isSubtype(of: .image) {
+ return Image(systemName: "photo")
+ }
+ else if fileType.isSubtype(of: .archive) {
+ return Image(systemName: "doc.zipper")
+ }
+ else if fileType.isSubtype(of: .text) {
+ return Image(systemName: "doc.text")
+ }
+ else {
+ return Image(systemName: "doc")
+ }
+ }
+
+ return Image(systemName: "doc")
+ }
+ #elseif os(macOS)
+ private func fileIcon(filename: String) -> Image {
+ Image(nsImage: NSWorkspace.shared.icon(for: UTType(filenameExtension: (filename as NSString).pathExtension) ?? UTType.content))
+ }
+ #endif
+
+
+ var body: some View {
+ fileIcon(filename: filename)
+ .resizable()
+ .scaledToFit()
+ }
+}
diff --git a/Hotline/Shared/URLAdditions.swift b/Hotline/Shared/URLAdditions.swift
new file mode 100644
index 0000000..7eebb46
--- /dev/null
+++ b/Hotline/Shared/URLAdditions.swift
@@ -0,0 +1,42 @@
+import Foundation
+
+extension URL {
+ func generateUniqueFilePath(filename base: String) -> String {
+ let fileManager = FileManager.default
+ var finalName = base
+ var counter = 2
+
+ // Helper function to generate a new filename with a counter
+ func makeFileName() -> String {
+ let baseName = (base as NSString).deletingPathExtension
+ let extensionName = (base as NSString).pathExtension
+ return extensionName.isEmpty ? "\(baseName) \(counter)" : "\(baseName) \(counter).\(extensionName)"
+ }
+
+ // Check if file exists and append counter until a unique name is found
+ var filePath = self.appending(component: finalName).path(percentEncoded: false)
+ while fileManager.fileExists(atPath: filePath) {
+ finalName = makeFileName()
+ filePath = self.appending(component: finalName).path(percentEncoded: false)
+ counter += 1
+ }
+
+ return filePath
+ }
+
+// private func prepareDownloadFile(name: String) -> Bool {
+// let folderURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0]
+// let filePath = findUniqueFilePath(base: name, at: folderURL)
+//
+// if FileManager.default.createFile(atPath: filePath, contents: nil) {
+// if let h = FileHandle(forWritingAtPath: filePath) {
+// self.filePath = filePath
+// self.fileHandle = h
+// self.fileProgress?.fileURL = URL(filePath: filePath).resolvingSymlinksInPath()
+// return true
+// }
+// }
+//
+// return false
+// }
+}