diff options
| author | Dustin Mierau <dustin@mierau.me> | 2025-10-27 11:03:49 -0700 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2025-10-27 11:03:49 -0700 |
| commit | 5924204ab2956d3947ca19c07585b81173226a4f (patch) | |
| tree | 68e2aa644f54bd5acfc02894e1f7f44f7da184e7 | |
| parent | 2b278f662326b44d86d84ae3e11ff0b770757f45 (diff) | |
Add spinning globe to TrackerView. Continue cleanup of NetSocketNew.
| -rw-r--r-- | Hotline.xcodeproj/project.pbxproj | 4 | ||||
| -rw-r--r-- | Hotline/Library/NetSocketNew.swift | 4 | ||||
| -rw-r--r-- | Hotline/Library/SpinningGlobeView.swift | 90 | ||||
| -rw-r--r-- | Hotline/macOS/TrackerView.swift | 21 |
4 files changed, 106 insertions, 13 deletions
diff --git a/Hotline.xcodeproj/project.pbxproj b/Hotline.xcodeproj/project.pbxproj index 2aafcfe..6368fe0 100644 --- a/Hotline.xcodeproj/project.pbxproj +++ b/Hotline.xcodeproj/project.pbxproj @@ -76,6 +76,7 @@ DAC3D9832BC33FD000A727C9 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC3D9822BC33FD000A727C9 /* AppState.swift */; }; DAC6B2E02EAC6236004E2CBA /* ChatStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC6B2DE2EAC6236004E2CBA /* ChatStore.swift */; }; DAC6B2E22EAEE9FE004E2CBA /* NetSocketNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC6B2E12EAEE9EF004E2CBA /* NetSocketNew.swift */; }; + DAC6B2E42EAFE92F004E2CBA /* SpinningGlobeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC6B2E32EAFE92F004E2CBA /* SpinningGlobeView.swift */; }; DAC87F072C5010E80060FADF /* HotlineExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC87F062C5010E80060FADF /* HotlineExtensions.swift */; }; DACCE5E12EABE4B4008CDD92 /* AppUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DACCE5E02EABE4B4008CDD92 /* AppUpdate.swift */; platformFilters = (macos, ); }; DACCE5E32EABE86A008CDD92 /* AppUpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DACCE5E22EABE86A008CDD92 /* AppUpdateView.swift */; platformFilters = (macos, ); }; @@ -165,6 +166,7 @@ DAC3D9822BC33FD000A727C9 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; }; DAC6B2DE2EAC6236004E2CBA /* ChatStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatStore.swift; sourceTree = "<group>"; }; DAC6B2E12EAEE9EF004E2CBA /* NetSocketNew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetSocketNew.swift; sourceTree = "<group>"; }; + DAC6B2E32EAFE92F004E2CBA /* SpinningGlobeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinningGlobeView.swift; sourceTree = "<group>"; }; DAC87F062C5010E80060FADF /* HotlineExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlineExtensions.swift; sourceTree = "<group>"; }; DACCE5E02EABE4B4008CDD92 /* AppUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdate.swift; sourceTree = "<group>"; }; DACCE5E22EABE86A008CDD92 /* AppUpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateView.swift; sourceTree = "<group>"; }; @@ -278,6 +280,7 @@ isa = PBXGroup; children = ( DAC6B2E12EAEE9EF004E2CBA /* NetSocketNew.swift */, + DAC6B2E32EAFE92F004E2CBA /* SpinningGlobeView.swift */, DAB4D87A2B4B78310048A05C /* FileImageView.swift */, DAB4D8812B4C8FED0048A05C /* FileIconView.swift */, DAB4D8832B4CABEF0048A05C /* DataAdditions.swift */, @@ -491,6 +494,7 @@ DA7725412B21435B006C5ABB /* ObservableScrollView.swift in Sources */, DAE734FF2B2E6750000C56F6 /* ChatView.swift in Sources */, 11F8288B2BF9428100216BA0 /* AccountManagerView.swift in Sources */, + DAC6B2E42EAFE92F004E2CBA /* SpinningGlobeView.swift in Sources */, DA4930BD2B4F8DD700822D0B /* NetSocket.swift in Sources */, DAC002192B21630900A6C290 /* SwiftUIExtensions.swift in Sources */, DADDB28F2B238D850024040D /* Hotline.swift in Sources */, diff --git a/Hotline/Library/NetSocketNew.swift b/Hotline/Library/NetSocketNew.swift index d4507b8..8873ee6 100644 --- a/Hotline/Library/NetSocketNew.swift +++ b/Hotline/Library/NetSocketNew.swift @@ -1108,7 +1108,7 @@ fileprivate extension NetSocketNew { let values = try url.resourceValues(forKeys: [.isRegularFileKey, .fileSizeKey]) guard values.isRegularFile == true else { throw NetSocketError.failed(underlying: NSError( - domain: "CleanSockets", code: 1001, + domain: "NetSocket", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Not a regular file: \(url.path)"] )) } @@ -1116,7 +1116,7 @@ fileprivate extension NetSocketNew { let attrs = try FileManager.default.attributesOfItem(atPath: url.path) if let n = attrs[.size] as? NSNumber { return n.int64Value } throw NetSocketError.failed(underlying: NSError( - domain: "CleanSockets", code: 1002, + domain: "NetSocket", code: 1002, userInfo: [NSLocalizedDescriptionKey: "Unable to determine file size for \(url.lastPathComponent)"] )) } diff --git a/Hotline/Library/SpinningGlobeView.swift b/Hotline/Library/SpinningGlobeView.swift new file mode 100644 index 0000000..bccb969 --- /dev/null +++ b/Hotline/Library/SpinningGlobeView.swift @@ -0,0 +1,90 @@ +import SwiftUI + +/// An animated globe icon that cycles through different world regions +/// +/// Displays a spinning globe effect by cycling through SF Symbol images showing +/// different parts of the world. Useful for indicating network activity or global content. +/// +/// Example: +/// ```swift +/// SpinningGlobeView() +/// .frame(width: 16, height: 16) +/// +/// SpinningGlobeView(frameDelay: 0.5) +/// .frame(width: 24, height: 24) +/// ``` +struct SpinningGlobeView: View { + /// Delay between frames in seconds (default: 0.3) + let frameDelay: TimeInterval + + /// SF Symbol names for each frame of the globe animation + private let globeFrames = [ + "globe.americas.fill", + "globe.europe.africa.fill", + "globe.central.south.asia.fill", + "globe.asia.australia.fill" + ] + + @State private var currentFrameIndex = 0 + @State private var animationTask: Task<Void, Never>? + + init(frameDelay: TimeInterval = 0.25) { + self.frameDelay = frameDelay + } + + var body: some View { + Image(systemName: globeFrames[currentFrameIndex]) + .onAppear { + startAnimation() + } + .onDisappear { + stopAnimation() + } + } + + private func startAnimation() { + animationTask = Task { + while !Task.isCancelled { + try? await Task.sleep(nanoseconds: UInt64(frameDelay * 1_000_000_000)) + + guard !Task.isCancelled else { break } + + currentFrameIndex = (currentFrameIndex + 1) % globeFrames.count + } + } + } + + private func stopAnimation() { + animationTask?.cancel() + animationTask = nil + } +} + +#Preview { + VStack(spacing: 20) { + HStack(spacing: 20) { + SpinningGlobeView() + .frame(width: 12, height: 12) + + SpinningGlobeView() + .frame(width: 16, height: 16) + + SpinningGlobeView() + .frame(width: 24, height: 24) + } + + Text("Different frame delays:") + + HStack(spacing: 20) { + SpinningGlobeView(frameDelay: 0.1) + .frame(width: 16, height: 16) + + SpinningGlobeView(frameDelay: 0.3) + .frame(width: 16, height: 16) + + SpinningGlobeView(frameDelay: 0.6) + .frame(width: 16, height: 16) + } + } + .padding() +} diff --git a/Hotline/macOS/TrackerView.swift b/Hotline/macOS/TrackerView.swift index ae653b8..251248f 100644 --- a/Hotline/macOS/TrackerView.swift +++ b/Hotline/macOS/TrackerView.swift @@ -755,19 +755,18 @@ struct TrackerItemView: View { if isExpanded && count > 0 { HStack(spacing: 4) { Text(String(count)) - - Image(systemName: "globe.americas.fill") - .resizable() - .scaledToFit() + + SpinningGlobeView() + .fontWeight(.semibold) .frame(width: 12, height: 12) - .opacity(0.5) +// .opacity(0.5) } - - .padding(.horizontal, 8) - .padding(.vertical, 2) - .foregroundStyle(.secondary) - .background(.quinary) - .clipShape(.capsule) + + .padding(.horizontal, 8) + .padding(.vertical, 2) + .foregroundStyle(.secondary) + .background(.quinary) + .clipShape(.capsule) } case .server: Image(systemName: "bookmark.fill") |