aboutsummaryrefslogtreecommitdiff
path: root/Hotline/Library/Views/SpinningGlobeView.swift
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/Views/SpinningGlobeView.swift
parent786a387d58fc66ae20716a9dee46b74dff8e6894 (diff)
Organize Library folder a bit. Update Tracker add/edit sheet design.
Diffstat (limited to 'Hotline/Library/Views/SpinningGlobeView.swift')
-rw-r--r--Hotline/Library/Views/SpinningGlobeView.swift90
1 files changed, 90 insertions, 0 deletions
diff --git a/Hotline/Library/Views/SpinningGlobeView.swift b/Hotline/Library/Views/SpinningGlobeView.swift
new file mode 100644
index 0000000..bccb969
--- /dev/null
+++ b/Hotline/Library/Views/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()
+}