Add animated splash screen with Gnafzgi Software branding

- Create SplashView with animated VU meter icon, wave background
- Show "presented by GNAFZGI SOFTWARE" on app startup
- Auto-dismiss after 2.5 seconds with fade transition
- Bump version to 1.3
This commit is contained in:
Claude
2025-12-14 15:46:22 +00:00
parent 5e0cc74aaf
commit 7a34c719e8
3 changed files with 284 additions and 14 deletions
@@ -18,6 +18,7 @@
A1000022229E3D000000001B /* HardwareView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1000023229E3D000000001C /* HardwareView.swift */; };
A1000024229E3D000000001D /* VUServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1000025229E3D000000001E /* VUServer.swift */; };
A1000026229E3D000000001F /* ServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1000027229E3D0000000020 /* ServerView.swift */; };
A1000028229E3D0000000021 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1000029229E3D0000000022 /* SplashView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -35,6 +36,7 @@
A1000023229E3D000000001C /* HardwareView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardwareView.swift; sourceTree = "<group>"; };
A1000025229E3D000000001E /* VUServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VUServer.swift; sourceTree = "<group>"; };
A1000027229E3D0000000020 /* ServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerView.swift; sourceTree = "<group>"; };
A1000029229E3D0000000022 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -69,6 +71,7 @@
A1000023229E3D000000001C /* HardwareView.swift */,
A1000025229E3D000000001E /* VUServer.swift */,
A1000027229E3D0000000020 /* ServerView.swift */,
A1000029229E3D0000000022 /* SplashView.swift */,
A100000E229E3D0000000007 /* Assets.xcassets */,
A100000F229E3D0000000008 /* AudioVUMeter.entitlements */,
A1000010229E3D0000000009 /* Info.plist */,
@@ -163,6 +166,7 @@
A1000022229E3D000000001B /* HardwareView.swift in Sources */,
A1000024229E3D000000001D /* VUServer.swift in Sources */,
A1000026229E3D000000001F /* ServerView.swift in Sources */,
A1000028229E3D0000000021 /* SplashView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -306,7 +310,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = com.audiotools.AudioVUMeter;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -335,7 +339,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = com.audiotools.AudioVUMeter;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -20,8 +20,12 @@ struct AudioVUMeterApp: App {
// Timer for updating hardware values
@State private var updateTimer: Timer?
// Splash screen state
@State private var showSplash = true
var body: some Scene {
WindowGroup {
ZStack {
ContentView()
.environmentObject(audioEngine)
.environmentObject(systemMonitor)
@@ -35,6 +39,17 @@ struct AudioVUMeterApp: App {
stopHardwareUpdateTimer()
vuServer.stop()
}
// Splash screen overlay
if showSplash {
SplashView {
withAnimation(.easeOut(duration: 0.5)) {
showSplash = false
}
}
.transition(.opacity)
}
}
}
.windowStyle(.hiddenTitleBar)
.windowResizability(.contentSize)
+251
View File
@@ -0,0 +1,251 @@
//
// SplashView.swift
// AudioVUMeter
//
// Splash screen shown at app startup
// Presented by Gnafzgi Software
//
import SwiftUI
struct SplashView: View {
@State private var isAnimating = false
@State private var showApp = false
@State private var logoScale: CGFloat = 0.5
@State private var logoOpacity: Double = 0
@State private var textOpacity: Double = 0
@State private var subtitleOpacity: Double = 0
@State private var waveOffset: CGFloat = 0
let onComplete: () -> Void
var body: some View {
ZStack {
// Background gradient
LinearGradient(
gradient: Gradient(colors: [
Color(red: 0.05, green: 0.05, blue: 0.1),
Color(red: 0.1, green: 0.08, blue: 0.15),
Color(red: 0.05, green: 0.05, blue: 0.1)
]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
// Animated wave background
WaveBackground(offset: waveOffset)
.opacity(0.3)
VStack(spacing: 30) {
Spacer()
// Animated VU Meter Icon
ZStack {
// Glow effect
Circle()
.fill(
RadialGradient(
gradient: Gradient(colors: [
Color.green.opacity(0.4),
Color.clear
]),
center: .center,
startRadius: 30,
endRadius: 80
)
)
.frame(width: 160, height: 160)
.blur(radius: 20)
.scaleEffect(isAnimating ? 1.2 : 1.0)
// Main icon
Image(systemName: "waveform.circle.fill")
.font(.system(size: 100))
.foregroundStyle(
LinearGradient(
colors: [.green, .cyan, .blue],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
.shadow(color: .green.opacity(0.5), radius: 20)
}
.scaleEffect(logoScale)
.opacity(logoOpacity)
// App Title
VStack(spacing: 8) {
Text("Audio VU Meter")
.font(.system(size: 36, weight: .bold, design: .rounded))
.foregroundStyle(
LinearGradient(
colors: [.white, .gray.opacity(0.8)],
startPoint: .top,
endPoint: .bottom
)
)
Text("Professional Audio Monitoring")
.font(.system(size: 14, weight: .medium, design: .rounded))
.foregroundColor(.gray)
}
.opacity(textOpacity)
Spacer()
// Presented by
VStack(spacing: 6) {
Text("presented by")
.font(.system(size: 11, weight: .regular, design: .rounded))
.foregroundColor(.gray.opacity(0.6))
.tracking(2)
Text("GNAFZGI SOFTWARE")
.font(.system(size: 16, weight: .bold, design: .rounded))
.foregroundStyle(
LinearGradient(
colors: [.cyan, .blue],
startPoint: .leading,
endPoint: .trailing
)
)
.tracking(3)
}
.opacity(subtitleOpacity)
.padding(.bottom, 50)
}
// Version badge
VStack {
Spacer()
HStack {
Spacer()
Text("v1.3")
.font(.system(size: 10, weight: .medium, design: .monospaced))
.foregroundColor(.gray.opacity(0.5))
.padding(8)
}
}
}
.frame(width: 400, height: 500)
.onAppear {
startAnimations()
}
}
private func startAnimations() {
// Wave animation (continuous)
withAnimation(.linear(duration: 8).repeatForever(autoreverses: false)) {
waveOffset = 1
}
// Logo animation
withAnimation(.spring(response: 0.8, dampingFraction: 0.6).delay(0.2)) {
logoScale = 1.0
logoOpacity = 1.0
}
// Pulse animation
withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true).delay(0.5)) {
isAnimating = true
}
// Title animation
withAnimation(.easeOut(duration: 0.8).delay(0.6)) {
textOpacity = 1.0
}
// Subtitle animation
withAnimation(.easeOut(duration: 0.8).delay(1.0)) {
subtitleOpacity = 1.0
}
// Auto-dismiss after delay
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
withAnimation(.easeOut(duration: 0.3)) {
onComplete()
}
}
}
}
// MARK: - Wave Background
struct WaveBackground: View {
let offset: CGFloat
var body: some View {
GeometryReader { geometry in
ZStack {
// First wave
WavePath(offset: offset, amplitude: 20, frequency: 1.5)
.stroke(
LinearGradient(
colors: [.green.opacity(0.3), .cyan.opacity(0.2)],
startPoint: .leading,
endPoint: .trailing
),
lineWidth: 2
)
// Second wave
WavePath(offset: offset + 0.3, amplitude: 15, frequency: 2)
.stroke(
LinearGradient(
colors: [.blue.opacity(0.2), .purple.opacity(0.2)],
startPoint: .leading,
endPoint: .trailing
),
lineWidth: 1.5
)
// Third wave
WavePath(offset: offset + 0.6, amplitude: 25, frequency: 1)
.stroke(
LinearGradient(
colors: [.cyan.opacity(0.15), .green.opacity(0.1)],
startPoint: .leading,
endPoint: .trailing
),
lineWidth: 1
)
}
}
}
}
// MARK: - Wave Path
struct WavePath: Shape {
var offset: CGFloat
var amplitude: CGFloat
var frequency: CGFloat
var animatableData: CGFloat {
get { offset }
set { offset = newValue }
}
func path(in rect: CGRect) -> Path {
var path = Path()
let midY = rect.midY
path.move(to: CGPoint(x: 0, y: midY))
for x in stride(from: 0, through: rect.width, by: 2) {
let relativeX = x / rect.width
let sine = sin((relativeX + offset) * .pi * 2 * frequency)
let y = midY + sine * amplitude
path.addLine(to: CGPoint(x: x, y: y))
}
return path
}
}
// MARK: - Preview
#Preview {
SplashView {
print("Splash complete")
}
}