Merge iOS and Mac app into one
This commit is contained in:
95
ABS Client/Audiobookshelf swift/Views/SplashView.swift
Normal file
95
ABS Client/Audiobookshelf swift/Views/SplashView.swift
Normal file
@@ -0,0 +1,95 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SplashView: View {
|
||||
@State private var appeared = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
#if os(iOS)
|
||||
Color(.systemBackground).ignoresSafeArea()
|
||||
#else
|
||||
Color(NSColor.windowBackgroundColor).ignoresSafeArea()
|
||||
#endif
|
||||
|
||||
VStack(spacing: 36) {
|
||||
|
||||
// ── Animated icon ────────────────────────────────────────
|
||||
ZStack {
|
||||
// Outer glow pulse
|
||||
Circle()
|
||||
.fill(Color.accentColor.opacity(0.15))
|
||||
.frame(width: 180, height: 180)
|
||||
.scaleEffect(appeared ? 1.0 : 0.2)
|
||||
.blur(radius: appeared ? 12 : 40)
|
||||
.animation(.easeOut(duration: 1.1), value: appeared)
|
||||
|
||||
// Ring border
|
||||
Circle()
|
||||
.strokeBorder(Color.accentColor.opacity(0.35), lineWidth: 1.5)
|
||||
.frame(width: 130, height: 130)
|
||||
.scaleEffect(appeared ? 1.0 : 0.4)
|
||||
.opacity(appeared ? 1 : 0)
|
||||
.animation(.easeOut(duration: 0.8).delay(0.1), value: appeared)
|
||||
|
||||
// Book icon springs into place
|
||||
Image(systemName: "books.vertical.fill")
|
||||
.font(.system(size: 58, weight: .regular))
|
||||
.foregroundStyle(Color.accentColor)
|
||||
.scaleEffect(appeared ? 1.0 : 0.1)
|
||||
.opacity(appeared ? 1 : 0)
|
||||
.animation(.spring(duration: 0.65, bounce: 0.55), value: appeared)
|
||||
.symbolEffect(.pulse.byLayer,
|
||||
options: .speed(0.5).repeating,
|
||||
value: appeared)
|
||||
}
|
||||
|
||||
// ── Text ─────────────────────────────────────────────────
|
||||
VStack(spacing: 6) {
|
||||
Text("ABS Client")
|
||||
.font(.title2.bold())
|
||||
.opacity(appeared ? 1 : 0)
|
||||
.offset(y: appeared ? 0 : 18)
|
||||
.animation(.easeOut(duration: 0.5).delay(0.28), value: appeared)
|
||||
|
||||
Text("Audiobookshelf")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.opacity(appeared ? 1 : 0)
|
||||
.offset(y: appeared ? 0 : 10)
|
||||
.animation(.easeOut(duration: 0.45).delay(0.42), value: appeared)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Loading dots at bottom ────────────────────────────────
|
||||
VStack {
|
||||
Spacer()
|
||||
LoadingDots()
|
||||
.opacity(appeared ? 1 : 0)
|
||||
.animation(.easeIn(duration: 0.3).delay(0.65), value: appeared)
|
||||
.padding(.bottom, 60)
|
||||
}
|
||||
}
|
||||
.onAppear { appeared = true }
|
||||
}
|
||||
}
|
||||
|
||||
private struct LoadingDots: View {
|
||||
@State private var phase: Int = 0
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 7) {
|
||||
ForEach(0..<3, id: \.self) { i in
|
||||
Circle()
|
||||
.fill(Color.accentColor.opacity(phase == i ? 0.9 : 0.3))
|
||||
.frame(width: 7, height: 7)
|
||||
.scaleEffect(phase == i ? 1.25 : 1.0)
|
||||
.animation(.easeInOut(duration: 0.35), value: phase)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
Timer.scheduledTimer(withTimeInterval: 0.38, repeats: true) { _ in
|
||||
phase = (phase + 1) % 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user