aboutsummaryrefslogtreecommitdiff
path: root/Hotline/Library/Utility
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2025-11-07 12:44:55 -0800
committerDustin Mierau <dustin@mierau.me>2025-11-07 12:44:55 -0800
commita55318fa8d643160900bec3e6b14e7404c63497a (patch)
treeab6a9eca9a368b35e1490becc70f94ebde6ac271 /Hotline/Library/Utility
parent786a387d58fc66ae20716a9dee46b74dff8e6894 (diff)
Organize Library folder a bit. Update Tracker add/edit sheet design.
Diffstat (limited to 'Hotline/Library/Utility')
-rw-r--r--Hotline/Library/Utility/DAKeychain.swift81
-rw-r--r--Hotline/Library/Utility/NSWindowBridge.swift46
-rw-r--r--Hotline/Library/Utility/ObservableScrollView.swift38
-rw-r--r--Hotline/Library/Utility/TextDocument.swift31
4 files changed, 196 insertions, 0 deletions
diff --git a/Hotline/Library/Utility/DAKeychain.swift b/Hotline/Library/Utility/DAKeychain.swift
new file mode 100644
index 0000000..8031886
--- /dev/null
+++ b/Hotline/Library/Utility/DAKeychain.swift
@@ -0,0 +1,81 @@
+//
+// DAKeychain.swift
+// DAKeychain
+//
+// Created by Dejan on 28/02/2017.
+// Copyright © 2017 Dejan. All rights reserved.
+//
+
+import Foundation
+
+open class DAKeychain {
+
+ open var loggingEnabled = false
+
+ private init() {}
+ public static let shared = DAKeychain()
+
+ open subscript(key: String) -> String? {
+ get {
+ return load(withKey: key)
+ } set {
+ DispatchQueue.global().sync(flags: .barrier) {
+ self.save(newValue, forKey: key)
+ }
+ }
+ }
+
+ private func save(_ string: String?, forKey key: String) {
+ let query = keychainQuery(withKey: key)
+ let objectData: Data? = string?.data(using: .utf8, allowLossyConversion: false)
+
+ if SecItemCopyMatching(query, nil) == noErr {
+ if let dictData = objectData {
+ let status = SecItemUpdate(query, NSDictionary(dictionary: [kSecValueData: dictData]))
+ logPrint("Update status: ", status)
+ } else {
+ let status = SecItemDelete(query)
+ logPrint("Delete status: ", status)
+ }
+ } else {
+ if let dictData = objectData {
+ query.setValue(dictData, forKey: kSecValueData as String)
+ let status = SecItemAdd(query, nil)
+ logPrint("Update status: ", status)
+ }
+ }
+ }
+
+ private func load(withKey key: String) -> String? {
+ let query = keychainQuery(withKey: key)
+ query.setValue(kCFBooleanTrue, forKey: kSecReturnData as String)
+ query.setValue(kCFBooleanTrue, forKey: kSecReturnAttributes as String)
+
+ var result: CFTypeRef?
+ let status = SecItemCopyMatching(query, &result)
+
+ guard
+ let resultsDict = result as? NSDictionary,
+ let resultsData = resultsDict.value(forKey: kSecValueData as String) as? Data,
+ status == noErr
+ else {
+ logPrint("Load status: ", status)
+ return nil
+ }
+ return String(data: resultsData, encoding: .utf8)
+ }
+
+ private func keychainQuery(withKey key: String) -> NSMutableDictionary {
+ let result = NSMutableDictionary()
+ result.setValue(kSecClassGenericPassword, forKey: kSecClass as String)
+ result.setValue(key, forKey: kSecAttrService as String)
+ result.setValue(kSecAttrAccessibleWhenUnlocked, forKey: kSecAttrAccessible as String)
+ return result
+ }
+
+ private func logPrint(_ items: Any...) {
+ if loggingEnabled {
+ print(items)
+ }
+ }
+}
diff --git a/Hotline/Library/Utility/NSWindowBridge.swift b/Hotline/Library/Utility/NSWindowBridge.swift
new file mode 100644
index 0000000..8f766d9
--- /dev/null
+++ b/Hotline/Library/Utility/NSWindowBridge.swift
@@ -0,0 +1,46 @@
+import SwiftUI
+
+fileprivate class NSWindowAccessorView: NSView {
+ let executeBlock: (_ window: NSWindow? ) -> ()
+
+ init(_ inConfigFunction: @escaping (_ window: NSWindow? ) -> () ) {
+ executeBlock = inConfigFunction
+ super.init( frame: NSRect() )
+ }
+
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+
+ public override func viewDidMoveToWindow() {
+ super.viewDidMoveToWindow()
+ executeBlock( self.window ) // We pass it through even if it is nil.
+ }
+}
+
+public struct NSWindowAccessor: NSViewRepresentable {
+ var configCode: (_ window: NSWindow? ) -> ()
+
+ public init(_ configCode: @escaping (_: NSWindow?) -> Void) { self.configCode = configCode }
+ public func makeNSView(context: Context) -> NSView { return NSWindowAccessorView( configCode ) }
+ public func updateNSView(_ nsView: NSView, context: Context) {}
+}
+
+
+//import SwiftUI
+
+ /// A helper view you can embed once per window to run a closure
+/// with the underlying NSWindow reference.
+struct WindowConfigurator: NSViewRepresentable {
+ let configure: (NSWindow) -> Void
+
+ func makeNSView(context: Context) -> NSView {
+ let view = NSView()
+ DispatchQueue.main.async {
+ if let window = view.window {
+ configure(window)
+ }
+ }
+ return view
+ }
+
+ func updateNSView(_ nsView: NSView, context: Context) { }
+}
diff --git a/Hotline/Library/Utility/ObservableScrollView.swift b/Hotline/Library/Utility/ObservableScrollView.swift
new file mode 100644
index 0000000..a6f46cc
--- /dev/null
+++ b/Hotline/Library/Utility/ObservableScrollView.swift
@@ -0,0 +1,38 @@
+import SwiftUI
+
+struct ScrollViewOffsetPreferenceKey: PreferenceKey {
+ typealias Value = CGFloat
+ static var defaultValue = CGFloat.zero
+ static func reduce(value: inout Value, nextValue: () -> Value) {
+ value += nextValue()
+ }
+}
+
+struct ObservableScrollView<Content>: View where Content : View {
+ @Namespace var scrollSpace
+ @Binding var scrollOffset: CGFloat
+ let content: () -> Content
+
+ init(scrollOffset: Binding<CGFloat>,
+ @ViewBuilder content: @escaping () -> Content) {
+ _scrollOffset = scrollOffset
+ self.content = content
+ }
+
+ var body: some View {
+ ScrollView {
+ content()
+ .background(GeometryReader { geo in
+ let offset = -geo.frame(in: .named(scrollSpace)).minY
+ Color.clear
+ .preference(key: ScrollViewOffsetPreferenceKey.self,
+ value: offset)
+ })
+
+ }
+ .coordinateSpace(name: scrollSpace)
+ .onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in
+ scrollOffset = value
+ }
+ }
+}
diff --git a/Hotline/Library/Utility/TextDocument.swift b/Hotline/Library/Utility/TextDocument.swift
new file mode 100644
index 0000000..82727de
--- /dev/null
+++ b/Hotline/Library/Utility/TextDocument.swift
@@ -0,0 +1,31 @@
+import Foundation
+import UniformTypeIdentifiers
+import SwiftUI
+
+struct TextFile: FileDocument {
+ // tell the system we support only plain text
+ static var readableContentTypes = [UTType.plainText, UTType.utf8PlainText]
+
+ // by default our document is empty
+ var text = ""
+
+ // a simple initializer that creates new, empty documents
+ init(initialText: String = "") {
+ text = initialText
+ }
+
+ // this initializer loads data that has been saved previously
+ init(configuration: ReadConfiguration) throws {
+
+ if let data = configuration.file.regularFileContents {
+ if let str = String(data: data, encoding: .utf8) {
+ self.text = str
+ }
+ }
+ }
+
+ // this will be called when the system wants to write our data to disk
+ func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
+ return FileWrapper(regularFileWithContents: self.text.data(using: .utf8)!)
+ }
+}