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:
@@ -18,6 +18,7 @@
|
|||||||
A1000022229E3D000000001B /* HardwareView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1000023229E3D000000001C /* HardwareView.swift */; };
|
A1000022229E3D000000001B /* HardwareView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1000023229E3D000000001C /* HardwareView.swift */; };
|
||||||
A1000024229E3D000000001D /* VUServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1000025229E3D000000001E /* VUServer.swift */; };
|
A1000024229E3D000000001D /* VUServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1000025229E3D000000001E /* VUServer.swift */; };
|
||||||
A1000026229E3D000000001F /* ServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1000027229E3D0000000020 /* ServerView.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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
A1000023229E3D000000001C /* HardwareView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardwareView.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -69,6 +71,7 @@
|
|||||||
A1000023229E3D000000001C /* HardwareView.swift */,
|
A1000023229E3D000000001C /* HardwareView.swift */,
|
||||||
A1000025229E3D000000001E /* VUServer.swift */,
|
A1000025229E3D000000001E /* VUServer.swift */,
|
||||||
A1000027229E3D0000000020 /* ServerView.swift */,
|
A1000027229E3D0000000020 /* ServerView.swift */,
|
||||||
|
A1000029229E3D0000000022 /* SplashView.swift */,
|
||||||
A100000E229E3D0000000007 /* Assets.xcassets */,
|
A100000E229E3D0000000007 /* Assets.xcassets */,
|
||||||
A100000F229E3D0000000008 /* AudioVUMeter.entitlements */,
|
A100000F229E3D0000000008 /* AudioVUMeter.entitlements */,
|
||||||
A1000010229E3D0000000009 /* Info.plist */,
|
A1000010229E3D0000000009 /* Info.plist */,
|
||||||
@@ -163,6 +166,7 @@
|
|||||||
A1000022229E3D000000001B /* HardwareView.swift in Sources */,
|
A1000022229E3D000000001B /* HardwareView.swift in Sources */,
|
||||||
A1000024229E3D000000001D /* VUServer.swift in Sources */,
|
A1000024229E3D000000001D /* VUServer.swift in Sources */,
|
||||||
A1000026229E3D000000001F /* ServerView.swift in Sources */,
|
A1000026229E3D000000001F /* ServerView.swift in Sources */,
|
||||||
|
A1000028229E3D0000000021 /* SplashView.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -306,7 +310,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2;
|
MARKETING_VERSION = 1.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiotools.AudioVUMeter;
|
PRODUCT_BUNDLE_IDENTIFIER = com.audiotools.AudioVUMeter;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -335,7 +339,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.2;
|
MARKETING_VERSION = 1.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiotools.AudioVUMeter;
|
PRODUCT_BUNDLE_IDENTIFIER = com.audiotools.AudioVUMeter;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
|||||||
@@ -20,8 +20,12 @@ struct AudioVUMeterApp: App {
|
|||||||
// Timer for updating hardware values
|
// Timer for updating hardware values
|
||||||
@State private var updateTimer: Timer?
|
@State private var updateTimer: Timer?
|
||||||
|
|
||||||
|
// Splash screen state
|
||||||
|
@State private var showSplash = true
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
|
ZStack {
|
||||||
ContentView()
|
ContentView()
|
||||||
.environmentObject(audioEngine)
|
.environmentObject(audioEngine)
|
||||||
.environmentObject(systemMonitor)
|
.environmentObject(systemMonitor)
|
||||||
@@ -35,6 +39,17 @@ struct AudioVUMeterApp: App {
|
|||||||
stopHardwareUpdateTimer()
|
stopHardwareUpdateTimer()
|
||||||
vuServer.stop()
|
vuServer.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Splash screen overlay
|
||||||
|
if showSplash {
|
||||||
|
SplashView {
|
||||||
|
withAnimation(.easeOut(duration: 0.5)) {
|
||||||
|
showSplash = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.transition(.opacity)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.windowStyle(.hiddenTitleBar)
|
.windowStyle(.hiddenTitleBar)
|
||||||
.windowResizability(.contentSize)
|
.windowResizability(.contentSize)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user