vibetunnel/ios/VibeTunnel/Views/Common/LoadingView.swift
2025-06-23 14:58:11 +02:00

68 lines
2.3 KiB
Swift

import SwiftUI
/// Reusable loading indicator with message.
///
/// Displays an animated spinner with a customizable message,
/// styled to match the terminal theme.
struct LoadingView: View {
let message: String
let useUnicodeSpinner: Bool
@State private var isAnimating = false
@State private var spinnerFrame = 0
/// Unicode spinner frames matching web UI
private let spinnerFrames = ["", "", "", "", "", "", "", "", "", ""]
init(message: String, useUnicodeSpinner: Bool = false) {
self.message = message
self.useUnicodeSpinner = useUnicodeSpinner
}
var body: some View {
VStack(spacing: Theme.Spacing.large) {
if useUnicodeSpinner {
Text(spinnerFrames[spinnerFrame])
.font(Theme.Typography.terminalSystem(size: 24))
.foregroundColor(Theme.Colors.primaryAccent)
.onAppear {
startUnicodeAnimation()
}
} else {
ZStack {
Circle()
.stroke(Theme.Colors.cardBorder, lineWidth: 3)
.frame(width: 50, height: 50)
Circle()
.trim(from: 0, to: 0.2)
.stroke(Theme.Colors.primaryAccent, lineWidth: 3)
.frame(width: 50, height: 50)
.rotationEffect(Angle(degrees: isAnimating ? 360 : 0))
.animation(
Animation.linear(duration: 1)
.repeatForever(autoreverses: false),
value: isAnimating
)
}
}
Text(message)
.font(Theme.Typography.terminalSystem(size: 14))
.foregroundColor(Theme.Colors.terminalForeground.opacity(0.7))
}
.onAppear {
if !useUnicodeSpinner {
isAnimating = true
}
}
}
private func startUnicodeAnimation() {
Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { _ in
Task { @MainActor in
spinnerFrame = (spinnerFrame + 1) % spinnerFrames.count
}
}
}
}