diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-11-27 23:38:04 +0100 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-11-27 23:38:04 +0100 |
| commit | 213710bf5bd6413c747bf126db50816ef5de5a6e (patch) | |
| tree | 912f8cf87955a08077c92fea8ad934f50b7ab975 /Hotline/Utility | |
| parent | f466b21dc02f78c984ba6748e703f6780a7a0db4 (diff) | |
| parent | 6a95b53616a4abfa306ddce43151cf4fefbd20ed (diff) | |
Merge remote-tracking branch 'upstream/main'
Diffstat (limited to 'Hotline/Utility')
| -rw-r--r-- | Hotline/Utility/BookmarkDocument.swift | 32 | ||||
| -rw-r--r-- | Hotline/Utility/DAKeychain.swift | 82 | ||||
| -rw-r--r-- | Hotline/Utility/FoundationExtensions.swift | 87 | ||||
| -rw-r--r-- | Hotline/Utility/NSWindowBridge.swift | 25 | ||||
| -rw-r--r-- | Hotline/Utility/ObservableScrollView.swift | 42 | ||||
| -rw-r--r-- | Hotline/Utility/RegularExpressions.swift | 235 | ||||
| -rw-r--r-- | Hotline/Utility/SoundEffects.swift | 52 | ||||
| -rw-r--r-- | Hotline/Utility/SwiftUIExtensions.swift | 9 | ||||
| -rw-r--r-- | Hotline/Utility/TextDocument.swift | 31 |
9 files changed, 0 insertions, 595 deletions
diff --git a/Hotline/Utility/BookmarkDocument.swift b/Hotline/Utility/BookmarkDocument.swift deleted file mode 100644 index 43f251c..0000000 --- a/Hotline/Utility/BookmarkDocument.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation -import SwiftUI -import UniformTypeIdentifiers - -struct BookmarkDocument: FileDocument { - static var readableContentTypes: [UTType] { [.data, UTType(filenameExtension: "hlbm")!] } - static var writableContentTypes: [UTType] { [.data, UTType(filenameExtension: "hlbm")!] } - - var bookmark: Bookmark - - init(bookmark: Bookmark) { - self.bookmark = bookmark - } - - init(configuration: ReadConfiguration) throws { - guard configuration.file.isRegularFile, - let data = configuration.file.regularFileContents, - let fileName = configuration.file.preferredFilename, - let bookmark = Bookmark(fileData: data, name: (fileName as NSString).deletingPathExtension) - else { - throw CocoaError(.fileReadCorruptFile) - } - self.bookmark = bookmark - } - - func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { - let wrapper = FileWrapper(regularFileWithContents: self.bookmark.bookmarkFileData()!) - wrapper.fileAttributes[FileAttributeKey.hfsCreatorCode.rawValue] = "HTLC".fourCharCode() - wrapper.fileAttributes[FileAttributeKey.hfsTypeCode.rawValue] = "HTbm".fourCharCode() - return wrapper - } -} diff --git a/Hotline/Utility/DAKeychain.swift b/Hotline/Utility/DAKeychain.swift deleted file mode 100644 index aa71508..0000000 --- a/Hotline/Utility/DAKeychain.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// 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/Utility/FoundationExtensions.swift b/Hotline/Utility/FoundationExtensions.swift deleted file mode 100644 index b1cd1b9..0000000 --- a/Hotline/Utility/FoundationExtensions.swift +++ /dev/null @@ -1,87 +0,0 @@ -import Foundation -import SwiftUI - -enum Endianness { - case big - case little -} - -extension String { - - func convertToAttributedStringWithLinks() -> AttributedString { - let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: self) - let matches = self.ranges(of: RegularExpressions.relaxedLink) - for match in matches { - let matchString = String(self[match]) - if matchString.isEmailAddress() { - attributedString.addAttribute( - .link, value: "mailto:\(matchString)", range: NSRange(match, in: self)) - } else { - attributedString.addAttribute(.link, value: matchString, range: NSRange(match, in: self)) - } - // attributedString.addAttribute(.underlineStyle, value: 1, range: NSRange(match, in: self)) - } - return AttributedString(attributedString) - } - - func isEmailAddress() -> Bool { - self.wholeMatch(of: RegularExpressions.emailAddress) != nil - } - - func isWebURL() -> Bool { - guard let url = URL(string: self) else { - return false - } - switch url.scheme?.lowercased() { - case "http", "https": - return true - default: - return false - } - } - - func isImageURL() -> Bool { - guard let url = URL(string: self) else { - return false - } - - switch url.pathExtension.lowercased() { - case "jpg", "jpeg", "png", "gif": - return true - default: - return false - } - } - - func convertingLinksToMarkdown() -> String { - var cp = String(self) - cp.replace(RegularExpressions.relaxedLink) { match -> String in - let linkText = self[match.range] - var injectedScheme = "https://" - if (try? RegularExpressions.supportedLinkScheme.prefixMatch(in: linkText)) != nil { - injectedScheme = "" - } - - return "[\(linkText)](\(injectedScheme)\(linkText))" - } - return cp - } -} - -extension Binding where Value: OptionSet, Value == Value.Element { - func bindedValue(_ options: Value) -> Bool { - return wrappedValue.contains(options) - } - - func bind(_ options: Value) -> Binding<Bool> { - return .init { () -> Bool in - self.wrappedValue.contains(options) - } set: { newValue in - if newValue { - self.wrappedValue.insert(options) - } else { - self.wrappedValue.remove(options) - } - } - } -} diff --git a/Hotline/Utility/NSWindowBridge.swift b/Hotline/Utility/NSWindowBridge.swift deleted file mode 100644 index ad56105..0000000 --- a/Hotline/Utility/NSWindowBridge.swift +++ /dev/null @@ -1,25 +0,0 @@ -import SwiftUI - -private class NSWindowAccessorView: NSView { - let executeBlock: (_ window: NSWindow?) -> Void - - init(_ inConfigFunction: @escaping (_ window: NSWindow?) -> Void) { - 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?) -> Void - - 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) {} -} diff --git a/Hotline/Utility/ObservableScrollView.swift b/Hotline/Utility/ObservableScrollView.swift deleted file mode 100644 index 05688f1..0000000 --- a/Hotline/Utility/ObservableScrollView.swift +++ /dev/null @@ -1,42 +0,0 @@ -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/Utility/RegularExpressions.swift b/Hotline/Utility/RegularExpressions.swift deleted file mode 100644 index db705ae..0000000 --- a/Hotline/Utility/RegularExpressions.swift +++ /dev/null @@ -1,235 +0,0 @@ -import RegexBuilder - -struct RegularExpressions { - static let messageBoardDivider = Regex { - Capture { - OneOrMore { - CharacterClass(.newlineSequence) - } - ZeroOrMore { - CharacterClass(.whitespace, .newlineSequence) - } - Repeat(2...) { - CharacterClass(.anyOf("_-")) - } - ZeroOrMore { - CharacterClass(.whitespace) - } - OneOrMore { - CharacterClass(.newlineSequence) - } - } - } - - static let supportedLinkScheme = Regex { - Anchor.startOfLine - ChoiceOf { - "hotline" - "http" - "https" - } - "://" - }.ignoresCase().anchorsMatchLineEndings() - - static let relaxedLink = Regex { - ChoiceOf { - Anchor.startOfLine - Anchor.wordBoundary - } - Capture { - // scheme (optional) - Optionally { - ChoiceOf { - "hotline://" - "http://" - "https://" - } - } - // domain name - OneOrMore { - CharacterClass( - .anyOf(".-@"), - ("a"..."z"), - ("0"..."9") - ) - } - // top-level domain name - "." - ChoiceOf { - "com" - "net" - "org" - "edu" - "gov" - "mil" - "aero" - "asia" - "biz" - "cat" - "coop" - "info" - "int" - "jobs" - "mobi" - "museum" - "name" - "pizza" - "post" - "pro" - "red" - "tel" - "today" - "travel" - "garden" - "online" - "ai" - "be" - "by" - "ca" - "co" - "de" - "er" - "es" - "fr" - "gs" - "ie" - "im" - "in" - "io" - "is" - "it" - "jp" - "la" - "ly" - "ma" - "md" - "me" - "my" - "nl" - "ps" - "pt" - "ja" - "st" - "to" - "tv" - "uk" - "ws" - } - // Port - Optionally { - ":" - OneOrMore { - CharacterClass(.digit) - } - } - // path - ZeroOrMore { - CharacterClass( - .anyOf("#_-/.?=&%\\()[]"), - ("a"..."z"), - ("0"..."9") - ) - } - } - ChoiceOf { - Anchor.endOfLine - Anchor.wordBoundary - } - } - .anchorsMatchLineEndings() - .ignoresCase() - - static let emailAddress = Regex { - ChoiceOf { - Anchor.startOfLine - Anchor.wordBoundary - } - Capture { - // username - OneOrMore { - CharacterClass( - .anyOf(".-_"), - ("a"..."z"), - ("0"..."9") - ) - } - "@" - // domain name - OneOrMore { - CharacterClass( - .anyOf(".-"), - ("a"..."z"), - ("0"..."9") - ) - } - // top-level domain name - "." - ChoiceOf { - "com" - "net" - "org" - "edu" - "gov" - "mil" - "aero" - "asia" - "biz" - "cat" - "coop" - "info" - "int" - "jobs" - "mobi" - "museum" - "name" - "pizza" - "post" - "pro" - "red" - "tel" - "today" - "travel" - "garden" - "online" - "ai" - "be" - "by" - "ca" - "co" - "de" - "er" - "es" - "fr" - "gs" - "ie" - "im" - "in" - "io" - "is" - "it" - "jp" - "la" - "ly" - "ma" - "md" - "me" - "my" - "nl" - "ps" - "pt" - "ja" - "st" - "to" - "tv" - "uk" - "ws" - } - } - ChoiceOf { - Anchor.endOfLine - Anchor.wordBoundary - } - } - .anchorsMatchLineEndings() - .ignoresCase() -} diff --git a/Hotline/Utility/SoundEffects.swift b/Hotline/Utility/SoundEffects.swift deleted file mode 100644 index 74314e4..0000000 --- a/Hotline/Utility/SoundEffects.swift +++ /dev/null @@ -1,52 +0,0 @@ -import AVFAudio -import Foundation - -enum SoundEffects: String { - case loggedIn = "logged-in" - case chatMessage = "chat-message" - case transferComplete = "transfer-complete" - case userLogin = "user-login" - case userLogout = "user-logout" - case newNews = "new-news" - case serverMessage = "server-message" - case error = "error" -} - -@Observable -class SoundEffectPlayer: NSObject, AVAudioPlayerDelegate { - static let shared = SoundEffectPlayer() - - private var activeSounds: [AVAudioPlayer] = [] - - func playSoundEffect(_ name: SoundEffects) { - // Load a local sound file - guard - let soundFileURL = Bundle.main.url( - forResource: name.rawValue, - withExtension: "aiff" - ) - else { - return - } - - DispatchQueue.main.async { [weak self] in - guard let self = self else { - return - } - - if let soundEffect = try? AVAudioPlayer(contentsOf: soundFileURL) { - soundEffect.delegate = self - soundEffect.volume = 0.75 - soundEffect.play() - - self.activeSounds.append(soundEffect) - } - } - } - - func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { - if let i = self.activeSounds.firstIndex(of: player) { - self.activeSounds.remove(at: i) - } - } -} diff --git a/Hotline/Utility/SwiftUIExtensions.swift b/Hotline/Utility/SwiftUIExtensions.swift deleted file mode 100644 index 27697f8..0000000 --- a/Hotline/Utility/SwiftUIExtensions.swift +++ /dev/null @@ -1,9 +0,0 @@ -import SwiftUI - -extension Color { - init(hex: Int, opacity: Double = 1.0) { - self.init( - red: Double((hex >> 16) & 0xFF) / 255.0, green: Double((hex >> 8) & 0xFF) / 255.0, - blue: Double(hex & 0xFF) / 255.0, opacity: opacity) - } -} diff --git a/Hotline/Utility/TextDocument.swift b/Hotline/Utility/TextDocument.swift deleted file mode 100644 index a3591fd..0000000 --- a/Hotline/Utility/TextDocument.swift +++ /dev/null @@ -1,31 +0,0 @@ -import Foundation -import SwiftUI -import UniformTypeIdentifiers - -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)!) - } -} |