commit e1ce6d1f43d910aef6960c94af0052d618b958a6
parent bd3bf69ee2013b80afb96278658cc4db82a8157b
Author: triesap <tyson@radroots.org>
Date: Sat, 13 Jun 2026 13:51:05 -0700
app: add launch splash screen
- point the native launch screen at generated splash assets
- render bootstrap startup with the branded logomark
- add a UI-test-only splash hold for deterministic checks
- track the splash asset catalog resources
Diffstat:
8 files changed, 96 insertions(+), 7 deletions(-)
diff --git a/Radroots/App/AppEntry.swift b/Radroots/App/AppEntry.swift
@@ -65,11 +65,29 @@ private struct StartupFailureView: View {
}
private struct SplashView: View {
+ private let splashGlyphSize: CGFloat = 160
+
var body: some View {
ZStack {
- Color(.systemBackground).ignoresSafeArea()
- ProgressView().controlSize(.large)
+ Color("RadrootsSplashBackground")
+ .ignoresSafeArea()
+
+ Color.clear
+ .accessibilityElement()
+ .accessibilityLabel("Startup")
+ .accessibilityIdentifier("field_ios.bootstrap")
+
+ GeometryReader { proxy in
+ Image("RadrootsSplashLogomark")
+ .resizable()
+ .scaledToFit()
+ .frame(width: splashGlyphSize, height: splashGlyphSize)
+ .position(x: proxy.size.width / 2, y: proxy.size.height / 2)
+ .accessibilityLabel("Radroots")
+ .accessibilityIdentifier("field_ios.splash.logo")
+ }
+ .ignoresSafeArea()
}
- .accessibilityIdentifier("field_ios.bootstrap")
+ .accessibilityElement(children: .contain)
}
}
diff --git a/Radroots/App/AppState.swift b/Radroots/App/AppState.swift
@@ -84,6 +84,7 @@ public final class AppState: ObservableObject {
guard bootstrapPhase == .idle || isFailed else { return }
bootstrapPhase = .starting
do {
+ try await holdBootstrapSplashForUITestIfRequested()
if startupFailureWasRequested {
throw FieldAppRuntimeError.forcedStartupFailure
}
@@ -205,13 +206,35 @@ public final class AppState: ObservableObject {
return false
}
- private var startupFailureWasRequested: Bool {
+ private var uiTestWasRequested: Bool {
let arguments = ProcessInfo.processInfo.arguments
let environment = ProcessInfo.processInfo.environment
- guard environment["RADROOTS_FIELD_IOS_UI_TEST"] == "true" ||
- arguments.contains("--radroots-field-ios-ui-test") else {
+ return environment["RADROOTS_FIELD_IOS_UI_TEST"] == "true" ||
+ arguments.contains("--radroots-field-ios-ui-test")
+ }
+
+ private var uiTestBootstrapSplashHoldNanoseconds: UInt64? {
+ guard uiTestWasRequested else { return nil }
+ guard let raw = ProcessInfo.processInfo.environment["RADROOTS_FIELD_IOS_UI_TEST_BOOTSTRAP_SPLASH_HOLD_SECONDS"],
+ let seconds = Double(raw),
+ seconds.isFinite,
+ seconds > 0 else {
+ return nil
+ }
+ return UInt64(seconds * 1_000_000_000)
+ }
+
+ private func holdBootstrapSplashForUITestIfRequested() async throws {
+ guard let nanoseconds = uiTestBootstrapSplashHoldNanoseconds else { return }
+ try await Task.sleep(nanoseconds: nanoseconds)
+ }
+
+ private var startupFailureWasRequested: Bool {
+ guard uiTestWasRequested else {
return false
}
+ let arguments = ProcessInfo.processInfo.arguments
+ let environment = ProcessInfo.processInfo.environment
if BuildConfig.string(.runtimeMode) == "ui-test-startup-failure" {
return true
}
diff --git a/Radroots/Info.plist b/Radroots/Info.plist
@@ -41,7 +41,12 @@
</dict>
<key>UILaunchScreen</key>
- <dict/>
+ <dict>
+ <key>UIColorName</key>
+ <string>RadrootsSplashBackground</string>
+ <key>UIImageName</key>
+ <string>RadrootsSplashLogomark</string>
+ </dict>
<key>UISupportedInterfaceOrientations</key>
<array>
diff --git a/Radroots/Resources/Assets.xcassets/RadrootsSplashBackground.colorset/Contents.json b/Radroots/Resources/Assets.xcassets/RadrootsSplashBackground.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+ "colors": [
+ {
+ "color": {
+ "color-space": "srgb",
+ "components": {
+ "red": "0.333333",
+ "green": "0.600000",
+ "blue": "0.247059",
+ "alpha": "1.000000"
+ }
+ },
+ "idiom": "universal"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ }
+}
diff --git a/Radroots/Resources/Assets.xcassets/RadrootsSplashLogomark.imageset/Contents.json b/Radroots/Resources/Assets.xcassets/RadrootsSplashLogomark.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images": [
+ {
+ "filename": "RadrootsSplashLogomark.png",
+ "idiom": "universal",
+ "scale": "1x"
+ },
+ {
+ "filename": "RadrootsSplashLogomark@2x.png",
+ "idiom": "universal",
+ "scale": "2x"
+ },
+ {
+ "filename": "RadrootsSplashLogomark@3x.png",
+ "idiom": "universal",
+ "scale": "3x"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ }
+}
diff --git a/Radroots/Resources/Assets.xcassets/RadrootsSplashLogomark.imageset/RadrootsSplashLogomark.png b/Radroots/Resources/Assets.xcassets/RadrootsSplashLogomark.imageset/RadrootsSplashLogomark.png
Binary files differ.
diff --git a/Radroots/Resources/Assets.xcassets/RadrootsSplashLogomark.imageset/RadrootsSplashLogomark@2x.png b/Radroots/Resources/Assets.xcassets/RadrootsSplashLogomark.imageset/RadrootsSplashLogomark@2x.png
Binary files differ.
diff --git a/Radroots/Resources/Assets.xcassets/RadrootsSplashLogomark.imageset/RadrootsSplashLogomark@3x.png b/Radroots/Resources/Assets.xcassets/RadrootsSplashLogomark.imageset/RadrootsSplashLogomark@3x.png
Binary files differ.