fix: splash screen uses actual app icon in a circle (Wenza style)
Replaces the custom 3-bar SwiftUI drawing with UIImage(named:"AppIcon") clipped to a circle, matching the Wenza splash pattern exactly: - Icon bounces in with spring (scale 0.82→1, opacity 0→1) - "jarvis" wordmark fades in 320ms later - Whole screen fades out at 1.6s, onDismiss fires at 1.9s - Main app renders underneath so it's warm when the splash clears Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,33 +5,53 @@ import SwiftData
|
|||||||
import UIKit
|
import UIKit
|
||||||
import BackgroundTasks
|
import BackgroundTasks
|
||||||
|
|
||||||
// MARK: - Logo + splash
|
// MARK: - Splash
|
||||||
|
|
||||||
/// The three-bar logo that mirrors the app icon.
|
|
||||||
struct JarvisLogoMark: View {
|
|
||||||
var width: CGFloat = 180
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .center, spacing: 8) {
|
|
||||||
// Orange (short) · White (longest) · Grey (medium) — same ratios as icon
|
|
||||||
Capsule().fill(Palette.orange)
|
|
||||||
.frame(width: width * 0.48, height: width * 0.075)
|
|
||||||
.shadow(color: Palette.orange.opacity(0.55), radius: 10, y: 2)
|
|
||||||
Capsule().fill(Color.white)
|
|
||||||
.frame(width: width, height: width * 0.075)
|
|
||||||
Capsule().fill(Color(hex: "888888"))
|
|
||||||
.frame(width: width * 0.74, height: width * 0.075)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SplashView: View {
|
struct SplashView: View {
|
||||||
|
var onDismiss: () -> Void
|
||||||
|
|
||||||
|
@State private var iconScale: CGFloat = 0.82
|
||||||
|
@State private var iconOpacity: Double = 0
|
||||||
|
@State private var markOpacity: Double = 0
|
||||||
|
@State private var rootOpacity: Double = 1
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color(hex: "0A0A0A").ignoresSafeArea()
|
Color(hex: "0A0A0A").ignoresSafeArea()
|
||||||
VStack(spacing: 28) {
|
VStack(spacing: 22) {
|
||||||
JarvisLogoMark(width: 180)
|
// App icon displayed as a circle
|
||||||
JarvisWordmark(size: 34)
|
Group {
|
||||||
|
if let img = UIImage(named: "AppIcon") {
|
||||||
|
Image(uiImage: img)
|
||||||
|
.resizable()
|
||||||
|
} else {
|
||||||
|
Circle().fill(Palette.surface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(width: 108, height: 108)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.shadow(color: Color.black.opacity(0.4), radius: 20, y: 6)
|
||||||
|
.scaleEffect(iconScale)
|
||||||
|
.opacity(iconOpacity)
|
||||||
|
|
||||||
|
JarvisWordmark(size: 28)
|
||||||
|
.opacity(markOpacity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.opacity(rootOpacity)
|
||||||
|
.onAppear {
|
||||||
|
withAnimation(.spring(response: 0.48, dampingFraction: 0.74).delay(0.08)) {
|
||||||
|
iconScale = 1
|
||||||
|
iconOpacity = 1
|
||||||
|
}
|
||||||
|
withAnimation(.easeOut(duration: 0.28).delay(0.32)) {
|
||||||
|
markOpacity = 1
|
||||||
|
}
|
||||||
|
withAnimation(.easeIn(duration: 0.26).delay(1.6)) {
|
||||||
|
rootOpacity = 0
|
||||||
|
}
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.9) {
|
||||||
|
onDismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,7 +89,7 @@ struct JarvisApp: App {
|
|||||||
@StateObject private var connectivity = ConnectivitySettings.shared
|
@StateObject private var connectivity = ConnectivitySettings.shared
|
||||||
@StateObject private var connManager = ConnectivityManager.shared
|
@StateObject private var connManager = ConnectivityManager.shared
|
||||||
@StateObject private var notifications = NotificationManager.shared
|
@StateObject private var notifications = NotificationManager.shared
|
||||||
@State private var showSplash = true
|
@State private var splashDone = false
|
||||||
|
|
||||||
// Single ModelContainer instance shared with BackgroundRefreshManager.
|
// Single ModelContainer instance shared with BackgroundRefreshManager.
|
||||||
private let container: ModelContainer = {
|
private let container: ModelContainer = {
|
||||||
@@ -81,21 +101,19 @@ struct JarvisApp: App {
|
|||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ZStack {
|
ZStack {
|
||||||
if showSplash {
|
// Main app always rendered underneath so it's ready when splash fades.
|
||||||
SplashView()
|
Group {
|
||||||
.transition(.opacity)
|
if settings.isConfigured {
|
||||||
} else {
|
RootTabView()
|
||||||
Group {
|
} else {
|
||||||
if settings.isConfigured {
|
OnboardingView()
|
||||||
RootTabView()
|
|
||||||
} else {
|
|
||||||
OnboardingView()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.transition(.opacity)
|
}
|
||||||
|
|
||||||
|
if !splashDone {
|
||||||
|
SplashView { splashDone = true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animation(.easeOut(duration: 0.5), value: showSplash)
|
|
||||||
.preferredColorScheme(appearanceMode.colorScheme)
|
.preferredColorScheme(appearanceMode.colorScheme)
|
||||||
.environmentObject(settings)
|
.environmentObject(settings)
|
||||||
.environmentObject(store)
|
.environmentObject(store)
|
||||||
@@ -109,9 +127,6 @@ struct JarvisApp: App {
|
|||||||
connManager.start()
|
connManager.start()
|
||||||
await connManager.resolveAndActivate()
|
await connManager.resolveAndActivate()
|
||||||
await notifications.bootstrap()
|
await notifications.bootstrap()
|
||||||
// Hold splash until setup is done, minimum 1.3 s for legibility.
|
|
||||||
try? await Task.sleep(nanoseconds: 1_300_000_000)
|
|
||||||
showSplash = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.modelContainer(container)
|
.modelContainer(container)
|
||||||
|
|||||||
Reference in New Issue
Block a user