capacitor-lottie-splash 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/android/src/main/java/com/headcount/plugins/lottiesplash/LottieSplashCoordinator.kt +30 -48
- package/android/src/main/java/com/headcount/plugins/lottiesplash/LottieSplashPlugin.kt +1 -2
- package/dist/esm/definitions.d.ts +1 -1
- package/dist/esm/state-machine.d.ts +0 -1
- package/dist/esm/state-machine.js +0 -6
- package/dist/esm/web.d.ts +1 -2
- package/dist/esm/web.js +23 -33
- package/dist/plugin.cjs.js +23 -39
- package/dist/plugin.js +23 -39
- package/ios/Sources/LottieSplashPlugin/LottieSplashCoordinator.swift +16 -44
- package/ios/Sources/LottieSplashPlugin/LottieSplashPlugin.swift +0 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,9 +101,9 @@ The documented HeadCount defaults are light `#f0f0f0 → #f5f5f5` and dark `#1e1
|
|
|
101
101
|
|
|
102
102
|
## Recovery and accessibility
|
|
103
103
|
|
|
104
|
-
When playback completes before readiness, its final frame holds. After one second, a subtle indeterminate progress rail appears beneath the mark
|
|
104
|
+
Each presentation fades in over 500 milliseconds before Lottie playback begins, then fades out over 500 milliseconds when the application is ready. When playback completes before readiness, its final frame holds. After one second, a subtle indeterminate progress rail appears beneath the mark and remains visible until the application is ready. Reduced-motion and asset failures retain the theme-matched native cover instead of exposing a blank screen.
|
|
105
105
|
|
|
106
|
-
For a staged native/over-the-air rollout, the consuming app must keep its web bundle and native host compatible. The native cover stays visible until the app-ready latch is set
|
|
106
|
+
For a staged native/over-the-air rollout, the consuming app must keep its web bundle and native host compatible. The native cover stays visible until the app-ready latch is set.
|
|
107
107
|
|
|
108
108
|
## Compatibility
|
|
109
109
|
|
|
@@ -12,10 +12,7 @@ import android.view.View
|
|
|
12
12
|
import android.view.ViewGroup
|
|
13
13
|
import android.view.animation.AccelerateDecelerateInterpolator
|
|
14
14
|
import android.view.animation.LinearInterpolator
|
|
15
|
-
import android.widget.Button
|
|
16
15
|
import android.widget.FrameLayout
|
|
17
|
-
import android.widget.LinearLayout
|
|
18
|
-
import android.widget.TextView
|
|
19
16
|
import com.airbnb.lottie.LottieAnimationView
|
|
20
17
|
import com.airbnb.lottie.LottieCompositionFactory
|
|
21
18
|
import androidx.activity.ComponentActivity
|
|
@@ -23,7 +20,7 @@ import java.util.UUID
|
|
|
23
20
|
import kotlin.math.max
|
|
24
21
|
import kotlin.math.roundToInt
|
|
25
22
|
|
|
26
|
-
enum class AndroidSplashPhase { PREPARING, PLAYING, HOLDING, HIDING, HIDDEN
|
|
23
|
+
enum class AndroidSplashPhase { PREPARING, PLAYING, HOLDING, HIDING, HIDDEN }
|
|
27
24
|
enum class AndroidSplashPlayback { PLAY, HOLD_FINAL_FRAME }
|
|
28
25
|
|
|
29
26
|
/** A small indeterminate rail that reads as loading without covering the finished logo. */
|
|
@@ -82,7 +79,7 @@ object LottieSplashCoordinator {
|
|
|
82
79
|
// incorrectly dismissing a healthy native cover during that startup work.
|
|
83
80
|
private const val WEB_ADOPTION_GRACE_MS = 30_000L
|
|
84
81
|
private const val PROGRESS_DELAY_MS = 1_000L
|
|
85
|
-
private const val
|
|
82
|
+
private const val COVER_FADE_IN_MS = 500L
|
|
86
83
|
private const val COVER_FADE_OUT_MS = 500L
|
|
87
84
|
private val main = Handler(Looper.getMainLooper())
|
|
88
85
|
|
|
@@ -92,10 +89,10 @@ object LottieSplashCoordinator {
|
|
|
92
89
|
private var hostForeground = false
|
|
93
90
|
private var systemHandoffComplete = true
|
|
94
91
|
private var compositionReady = false
|
|
92
|
+
private var coverFadeInStarted = false
|
|
93
|
+
private var coverFadeInComplete = false
|
|
95
94
|
private var requestedPlayback = AndroidSplashPlayback.PLAY
|
|
96
95
|
private var loadingRail: LottieSplashLoadingRail? = null
|
|
97
|
-
private var recoveryMessage: TextView? = null
|
|
98
|
-
private var recoveryRetry: Button? = null
|
|
99
96
|
private var presentationId: String? = null
|
|
100
97
|
private var phase = AndroidSplashPhase.HIDDEN
|
|
101
98
|
private var playbackSettled = false
|
|
@@ -106,16 +103,13 @@ object LottieSplashCoordinator {
|
|
|
106
103
|
private var completion: ((String, String?) -> Unit)? = null
|
|
107
104
|
private var pendingHide: ((Throwable?) -> Unit)? = null
|
|
108
105
|
private var progressTask: Runnable? = null
|
|
109
|
-
private var recoveryTask: Runnable? = null
|
|
110
106
|
private var adoptionTask: Runnable? = null
|
|
111
107
|
private var adoptedByWeb = false
|
|
112
108
|
private var progressRemainingMs = PROGRESS_DELAY_MS
|
|
113
|
-
private var recoveryRemainingMs = RECOVERY_DELAY_MS
|
|
114
109
|
private var waitingClockStartedAtMs = 0L
|
|
115
110
|
private var waitingClockRunning = false
|
|
116
111
|
|
|
117
112
|
var onStateChange: (() -> Unit)? = null
|
|
118
|
-
var onRecoveryRequested: (() -> Unit)? = null
|
|
119
113
|
|
|
120
114
|
/**
|
|
121
115
|
* Called by AndroidX while deciding whether its system-owned launch surface may leave.
|
|
@@ -144,6 +138,8 @@ object LottieSplashCoordinator {
|
|
|
144
138
|
hostForeground = activity.hasWindowFocus()
|
|
145
139
|
systemHandoffComplete = !dismissWhenUnadopted
|
|
146
140
|
compositionReady = false
|
|
141
|
+
coverFadeInStarted = false
|
|
142
|
+
coverFadeInComplete = false
|
|
147
143
|
requestedPlayback = playback
|
|
148
144
|
presentationId = UUID.randomUUID().toString()
|
|
149
145
|
phase = AndroidSplashPhase.PREPARING
|
|
@@ -153,8 +149,6 @@ object LottieSplashCoordinator {
|
|
|
153
149
|
playbackOutcome = "static-fallback"
|
|
154
150
|
playbackReason = null
|
|
155
151
|
loadingRail = null
|
|
156
|
-
recoveryMessage = null
|
|
157
|
-
recoveryRetry = null
|
|
158
152
|
|
|
159
153
|
val backgroundStart = if (darkTheme) Color.rgb(30, 30, 30) else Color.rgb(240, 240, 240)
|
|
160
154
|
root.setBackgroundColor(backgroundStart)
|
|
@@ -165,7 +159,7 @@ object LottieSplashCoordinator {
|
|
|
165
159
|
isClickable = true
|
|
166
160
|
isFocusable = true
|
|
167
161
|
contentDescription = "Loading application"
|
|
168
|
-
alpha =
|
|
162
|
+
alpha = 0f
|
|
169
163
|
background = GradientDrawable(
|
|
170
164
|
GradientDrawable.Orientation.LEFT_RIGHT,
|
|
171
165
|
if (darkTheme) intArrayOf(backgroundStart, Color.rgb(37, 37, 37))
|
|
@@ -182,6 +176,7 @@ object LottieSplashCoordinator {
|
|
|
182
176
|
overlay.post { updateAnimationLayout(overlay, lottie) }
|
|
183
177
|
cover = overlay
|
|
184
178
|
animation = lottie
|
|
179
|
+
if (systemHandoffComplete) beginCoverFadeIn(presentationId!!, overlay, lottie)
|
|
185
180
|
if (dismissWhenUnadopted) scheduleUnadoptedFallback(presentationId!!)
|
|
186
181
|
emit()
|
|
187
182
|
|
|
@@ -228,6 +223,7 @@ object LottieSplashCoordinator {
|
|
|
228
223
|
val id = presentationId ?: return
|
|
229
224
|
val existingCover = cover ?: return
|
|
230
225
|
val existingAnimation = animation ?: return
|
|
226
|
+
beginCoverFadeIn(id, existingCover, existingAnimation)
|
|
231
227
|
startPlaybackWhenVisible(id, existingCover, existingAnimation)
|
|
232
228
|
}
|
|
233
229
|
|
|
@@ -294,7 +290,7 @@ object LottieSplashCoordinator {
|
|
|
294
290
|
}
|
|
295
291
|
|
|
296
292
|
private fun startPlaybackWhenVisible(id: String, overlay: FrameLayout, lottie: LottieAnimationView) {
|
|
297
|
-
if (id != presentationId || cover !== overlay || animation !== lottie || !compositionReady || !hostForeground || !systemHandoffComplete || phase != AndroidSplashPhase.PREPARING) return
|
|
293
|
+
if (id != presentationId || cover !== overlay || animation !== lottie || !compositionReady || !coverFadeInComplete || !hostForeground || !systemHandoffComplete || phase != AndroidSplashPhase.PREPARING) return
|
|
298
294
|
val composition = lottie.composition ?: return
|
|
299
295
|
phase = AndroidSplashPhase.PLAYING
|
|
300
296
|
emit()
|
|
@@ -323,6 +319,22 @@ object LottieSplashCoordinator {
|
|
|
323
319
|
}
|
|
324
320
|
}
|
|
325
321
|
|
|
322
|
+
private fun beginCoverFadeIn(id: String, overlay: FrameLayout, lottie: LottieAnimationView) {
|
|
323
|
+
if (id != presentationId || cover !== overlay || animation !== lottie || coverFadeInStarted || !systemHandoffComplete) return
|
|
324
|
+
coverFadeInStarted = true
|
|
325
|
+
overlay.animate()
|
|
326
|
+
.alpha(1f)
|
|
327
|
+
.setDuration(if (ValueAnimator.areAnimatorsEnabled()) COVER_FADE_IN_MS else 0)
|
|
328
|
+
.setInterpolator(AccelerateDecelerateInterpolator())
|
|
329
|
+
.withEndAction {
|
|
330
|
+
if (id == presentationId && cover === overlay && animation === lottie) {
|
|
331
|
+
coverFadeInComplete = true
|
|
332
|
+
startPlaybackWhenVisible(id, overlay, lottie)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
.start()
|
|
336
|
+
}
|
|
337
|
+
|
|
326
338
|
private fun updateAnimationLayout(overlay: FrameLayout, lottie: LottieAnimationView) {
|
|
327
339
|
if (cover !== overlay || animation !== lottie || overlay.width <= 0 || overlay.height <= 0) return
|
|
328
340
|
val availableWidth = minOf(overlay.width.toFloat(), overlay.height * 0.75f)
|
|
@@ -354,7 +366,6 @@ object LottieSplashCoordinator {
|
|
|
354
366
|
private fun resetWaitingUi(id: String) {
|
|
355
367
|
cancelWaitingTasks()
|
|
356
368
|
progressRemainingMs = PROGRESS_DELAY_MS
|
|
357
|
-
recoveryRemainingMs = RECOVERY_DELAY_MS
|
|
358
369
|
scheduleWaitingUi(id)
|
|
359
370
|
}
|
|
360
371
|
|
|
@@ -371,24 +382,19 @@ object LottieSplashCoordinator {
|
|
|
371
382
|
waitingClockRunning = true
|
|
372
383
|
waitingClockStartedAtMs = SystemClock.elapsedRealtime()
|
|
373
384
|
progressTask = Runnable { showLoadingRail(id) }
|
|
374
|
-
recoveryTask = Runnable { showRecovery(id) }
|
|
375
385
|
if (loadingRail == null) main.postDelayed(progressTask!!, progressRemainingMs)
|
|
376
|
-
main.postDelayed(recoveryTask!!, recoveryRemainingMs)
|
|
377
386
|
}
|
|
378
387
|
|
|
379
388
|
private fun pauseWaitingClock() {
|
|
380
389
|
if (!waitingClockRunning) return
|
|
381
390
|
val elapsed = SystemClock.elapsedRealtime() - waitingClockStartedAtMs
|
|
382
391
|
progressRemainingMs = max(0, progressRemainingMs - elapsed)
|
|
383
|
-
recoveryRemainingMs = max(0, recoveryRemainingMs - elapsed)
|
|
384
392
|
cancelWaitingTasks()
|
|
385
393
|
}
|
|
386
394
|
|
|
387
395
|
private fun cancelWaitingTasks() {
|
|
388
396
|
progressTask?.let(main::removeCallbacks)
|
|
389
|
-
recoveryTask?.let(main::removeCallbacks)
|
|
390
397
|
progressTask = null
|
|
391
|
-
recoveryTask = null
|
|
392
398
|
waitingClockRunning = false
|
|
393
399
|
}
|
|
394
400
|
|
|
@@ -404,36 +410,10 @@ object LottieSplashCoordinator {
|
|
|
404
410
|
}
|
|
405
411
|
}
|
|
406
412
|
|
|
407
|
-
private fun showRecovery(id: String) {
|
|
408
|
-
recoveryRemainingMs = 0
|
|
409
|
-
if (presentationId != id || appReady || phase != AndroidSplashPhase.HOLDING) return
|
|
410
|
-
cancelWaitingTasks()
|
|
411
|
-
phase = AndroidSplashPhase.RECOVERY
|
|
412
|
-
loadingRail?.visibility = View.GONE
|
|
413
|
-
val context = cover?.context ?: return
|
|
414
|
-
val textColor = if (darkTheme) Color.WHITE else Color.rgb(30, 30, 30)
|
|
415
|
-
val message = TextView(context).apply { text = "Still starting…"; textSize = 16f; setTextColor(textColor); gravity = Gravity.CENTER }
|
|
416
|
-
val retry = Button(context).apply { text = "Try again"; setOnClickListener { onRecoveryRequested?.invoke() } }
|
|
417
|
-
val recoveryGroup = LinearLayout(context).apply {
|
|
418
|
-
orientation = LinearLayout.VERTICAL
|
|
419
|
-
gravity = Gravity.CENTER_HORIZONTAL
|
|
420
|
-
addView(message)
|
|
421
|
-
addView(retry)
|
|
422
|
-
}
|
|
423
|
-
recoveryMessage = message
|
|
424
|
-
recoveryRetry = retry
|
|
425
|
-
cover?.addView(recoveryGroup, FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM).apply { bottomMargin = dp(recoveryGroup, 48) })
|
|
426
|
-
recoveryGroup.announceForAccessibility("Still starting. Try again is available.")
|
|
427
|
-
emit()
|
|
428
|
-
}
|
|
429
|
-
|
|
430
413
|
private fun clearWaitingUi() {
|
|
431
414
|
cancelWaitingTasks()
|
|
432
415
|
loadingRail?.let { cover?.removeView(it) }
|
|
433
|
-
recoveryMessage?.parent?.let { parent -> (parent as? ViewGroup)?.let { cover?.removeView(it) } }
|
|
434
416
|
loadingRail = null
|
|
435
|
-
recoveryMessage = null
|
|
436
|
-
recoveryRetry = null
|
|
437
417
|
}
|
|
438
418
|
|
|
439
419
|
private fun dismiss(callback: (Throwable?) -> Unit) {
|
|
@@ -464,11 +444,11 @@ object LottieSplashCoordinator {
|
|
|
464
444
|
animation = null
|
|
465
445
|
hostActivity = null
|
|
466
446
|
loadingRail = null
|
|
467
|
-
recoveryMessage = null
|
|
468
|
-
recoveryRetry = null
|
|
469
447
|
presentationId = null
|
|
470
448
|
phase = AndroidSplashPhase.HIDDEN
|
|
471
449
|
compositionReady = false
|
|
450
|
+
coverFadeInStarted = false
|
|
451
|
+
coverFadeInComplete = false
|
|
472
452
|
emit()
|
|
473
453
|
callback(null)
|
|
474
454
|
}.start()
|
|
@@ -489,6 +469,8 @@ object LottieSplashCoordinator {
|
|
|
489
469
|
presentationId = null
|
|
490
470
|
phase = AndroidSplashPhase.HIDDEN
|
|
491
471
|
compositionReady = false
|
|
472
|
+
coverFadeInStarted = false
|
|
473
|
+
coverFadeInComplete = false
|
|
492
474
|
pendingHide = null
|
|
493
475
|
completion = null
|
|
494
476
|
emit()
|
|
@@ -20,7 +20,6 @@ class LottieSplashPlugin : Plugin() {
|
|
|
20
20
|
}
|
|
21
21
|
override fun load() {
|
|
22
22
|
LottieSplashCoordinator.onStateChange = { notifyListeners("stateChange", stateObject()) }
|
|
23
|
-
LottieSplashCoordinator.onRecoveryRequested = { bridge?.webView?.reload() }
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
@PluginMethod
|
|
@@ -89,7 +88,7 @@ class LottieSplashPlugin : Plugin() {
|
|
|
89
88
|
webView.postVisualStateCallback(SystemClock.uptimeMillis(), object : WebView.VisualStateCallback() {
|
|
90
89
|
override fun onComplete(requestId: Long) = finishHandoff()
|
|
91
90
|
})
|
|
92
|
-
// A stalled GPU/compositor must not leave an app-ready screen
|
|
91
|
+
// A stalled GPU/compositor must not leave an app-ready screen covered.
|
|
93
92
|
main.postDelayed(fallback, VISUAL_STATE_TIMEOUT_MS)
|
|
94
93
|
} catch (_: Throwable) {
|
|
95
94
|
finishHandoff()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PluginListenerHandle } from '@capacitor/core';
|
|
2
2
|
export type SplashRenderer = 'ios' | 'android' | 'web';
|
|
3
|
-
export type SplashPhase = 'preparing' | 'playing' | 'holding' | 'hiding' | 'hidden'
|
|
3
|
+
export type SplashPhase = 'preparing' | 'playing' | 'holding' | 'hiding' | 'hidden';
|
|
4
4
|
export type PlaybackOutcome = 'completed' | 'reduced-motion' | 'static-fallback';
|
|
5
5
|
export type SplashTheme = 'light' | 'dark';
|
|
6
6
|
export interface SplashAssets {
|
|
@@ -10,7 +10,6 @@ export declare class SplashStateMachine {
|
|
|
10
10
|
playing(presentationId: string): StateMachineSnapshot;
|
|
11
11
|
settlePlayback(presentationId: string, outcome: PlaybackOutcome, reason?: string): StateMachineSnapshot;
|
|
12
12
|
requestHide(presentationId: string): StateMachineSnapshot;
|
|
13
|
-
recovery(presentationId: string): StateMachineSnapshot;
|
|
14
13
|
hidden(presentationId: string): StateMachineSnapshot;
|
|
15
14
|
getPlaybackResult(presentationId: string): PlaybackResult | undefined;
|
|
16
15
|
current(): StateMachineSnapshot;
|
|
@@ -45,12 +45,6 @@ export class SplashStateMachine {
|
|
|
45
45
|
this.snapshot.phase = 'hiding';
|
|
46
46
|
return this.current();
|
|
47
47
|
}
|
|
48
|
-
recovery(presentationId) {
|
|
49
|
-
this.requireCurrent(presentationId);
|
|
50
|
-
if (!this.snapshot.appReady)
|
|
51
|
-
this.snapshot.phase = 'recovery';
|
|
52
|
-
return this.current();
|
|
53
|
-
}
|
|
54
48
|
hidden(presentationId) {
|
|
55
49
|
this.requireCurrent(presentationId);
|
|
56
50
|
this.snapshot = {
|
package/dist/esm/web.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare class LottieSplashWeb extends WebPlugin implements LottieSplashPl
|
|
|
6
6
|
private resolvePlayback?;
|
|
7
7
|
private overlay?;
|
|
8
8
|
private progressTimer?;
|
|
9
|
-
private
|
|
9
|
+
private playbackTimer?;
|
|
10
10
|
show(options: SplashShowOptions): Promise<Presentation>;
|
|
11
11
|
setLaunchTheme(_options: SplashLaunchThemeOptions): Promise<void>;
|
|
12
12
|
waitForCompletion(options: {
|
|
@@ -21,7 +21,6 @@ export declare class LottieSplashWeb extends WebPlugin implements LottieSplashPl
|
|
|
21
21
|
private playComposition;
|
|
22
22
|
private settle;
|
|
23
23
|
private showLoadingRail;
|
|
24
|
-
private showRecovery;
|
|
25
24
|
private removeOverlay;
|
|
26
25
|
private ensureCurrent;
|
|
27
26
|
private emitState;
|
package/dist/esm/web.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { WebPlugin } from '@capacitor/core';
|
|
2
2
|
import { SplashStateMachine } from './state-machine';
|
|
3
|
-
const RECOVERY_TIMEOUT_MS = 15_000;
|
|
4
3
|
const PROGRESS_DELAY_MS = 1_000;
|
|
4
|
+
const COVER_FADE_MS = 500;
|
|
5
5
|
export class LottieSplashWeb extends WebPlugin {
|
|
6
6
|
stateMachine = new SplashStateMachine();
|
|
7
7
|
playbackPromise;
|
|
8
8
|
resolvePlayback;
|
|
9
9
|
overlay;
|
|
10
10
|
progressTimer;
|
|
11
|
-
|
|
11
|
+
playbackTimer;
|
|
12
12
|
async show(options) {
|
|
13
13
|
const state = this.stateMachine.current();
|
|
14
14
|
if (state.visible && state.presentationId)
|
|
@@ -32,7 +32,7 @@ export class LottieSplashWeb extends WebPlugin {
|
|
|
32
32
|
this.emitState();
|
|
33
33
|
if (!state.playbackSettled)
|
|
34
34
|
await this.waitForCompletion(options);
|
|
35
|
-
this.removeOverlay(options.presentationId);
|
|
35
|
+
await this.removeOverlay(options.presentationId);
|
|
36
36
|
}
|
|
37
37
|
async getState() {
|
|
38
38
|
const { generation: _generation, ...state } = this.stateMachine.current();
|
|
@@ -49,10 +49,14 @@ export class LottieSplashWeb extends WebPlugin {
|
|
|
49
49
|
overlay.dataset.presentationId = presentationId;
|
|
50
50
|
Object.assign(overlay.style, {
|
|
51
51
|
position: 'fixed', inset: '0', zIndex: '2147483647', display: 'grid', placeItems: 'center',
|
|
52
|
-
background: this.backgroundFor(this.stateMachine.current().theme), overflow: 'hidden'
|
|
52
|
+
background: this.backgroundFor(this.stateMachine.current().theme), overflow: 'hidden',
|
|
53
|
+
opacity: '0', transition: `opacity ${COVER_FADE_MS}ms ease-in-out`
|
|
53
54
|
});
|
|
54
55
|
document.body.append(overlay);
|
|
55
56
|
this.overlay = overlay;
|
|
57
|
+
// Force the initial transparent style to paint before beginning the fade.
|
|
58
|
+
void overlay.offsetWidth;
|
|
59
|
+
overlay.style.opacity = '1';
|
|
56
60
|
// A generic package cannot ship HeadCount assets. The host supplies an approved local
|
|
57
61
|
// composition; absent or failed assets leave the opaque branded background in place.
|
|
58
62
|
if (!options.assets?.animationData && !options.assets?.animationPath) {
|
|
@@ -63,9 +67,13 @@ export class LottieSplashWeb extends WebPlugin {
|
|
|
63
67
|
this.settle(presentationId, 'reduced-motion', 'Reduced motion is enabled');
|
|
64
68
|
return;
|
|
65
69
|
}
|
|
66
|
-
this.
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
this.playbackTimer = window.setTimeout(() => {
|
|
71
|
+
if (this.stateMachine.current().presentationId !== presentationId || this.overlay !== overlay)
|
|
72
|
+
return;
|
|
73
|
+
this.stateMachine.playing(presentationId);
|
|
74
|
+
this.emitState();
|
|
75
|
+
void this.playComposition(options, presentationId);
|
|
76
|
+
}, COVER_FADE_MS);
|
|
69
77
|
}
|
|
70
78
|
async playComposition(options, presentationId) {
|
|
71
79
|
try {
|
|
@@ -104,7 +112,6 @@ export class LottieSplashWeb extends WebPlugin {
|
|
|
104
112
|
this.emitState();
|
|
105
113
|
if (!state.appReady) {
|
|
106
114
|
this.progressTimer = window.setTimeout(() => this.showLoadingRail(presentationId), PROGRESS_DELAY_MS);
|
|
107
|
-
this.recoveryTimer = window.setTimeout(() => this.showRecovery(presentationId), RECOVERY_TIMEOUT_MS);
|
|
108
115
|
}
|
|
109
116
|
}
|
|
110
117
|
catch {
|
|
@@ -127,32 +134,15 @@ export class LottieSplashWeb extends WebPlugin {
|
|
|
127
134
|
rail.append(segment);
|
|
128
135
|
this.overlay?.append(style, rail);
|
|
129
136
|
}
|
|
130
|
-
|
|
131
|
-
let state;
|
|
132
|
-
try {
|
|
133
|
-
state = this.stateMachine.recovery(presentationId);
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
if (state.presentationId !== presentationId || state.appReady)
|
|
139
|
-
return;
|
|
140
|
-
this.overlay?.querySelector('[data-splash-loading-rail]')?.remove();
|
|
141
|
-
const recovery = document.createElement('div');
|
|
142
|
-
recovery.style.cssText = 'position:absolute;bottom:12%;display:grid;gap:12px;place-items:center;color:inherit;font:500 16px system-ui;';
|
|
143
|
-
recovery.textContent = 'Still starting…';
|
|
144
|
-
const retry = document.createElement('button');
|
|
145
|
-
retry.type = 'button';
|
|
146
|
-
retry.textContent = 'Try again';
|
|
147
|
-
retry.onclick = () => window.location.reload();
|
|
148
|
-
recovery.append(retry);
|
|
149
|
-
this.overlay?.append(recovery);
|
|
150
|
-
this.emitState();
|
|
151
|
-
}
|
|
152
|
-
removeOverlay(presentationId) {
|
|
137
|
+
async removeOverlay(presentationId) {
|
|
153
138
|
window.clearTimeout(this.progressTimer);
|
|
154
|
-
window.clearTimeout(this.
|
|
155
|
-
this.overlay
|
|
139
|
+
window.clearTimeout(this.playbackTimer);
|
|
140
|
+
const overlay = this.overlay;
|
|
141
|
+
if (overlay) {
|
|
142
|
+
overlay.style.opacity = '0';
|
|
143
|
+
await new Promise(resolve => window.setTimeout(resolve, COVER_FADE_MS));
|
|
144
|
+
overlay.remove();
|
|
145
|
+
}
|
|
156
146
|
this.overlay = undefined;
|
|
157
147
|
this.stateMachine.hidden(presentationId);
|
|
158
148
|
this.emitState();
|
package/dist/plugin.cjs.js
CHANGED
|
@@ -53,12 +53,6 @@ class SplashStateMachine {
|
|
|
53
53
|
this.snapshot.phase = 'hiding';
|
|
54
54
|
return this.current();
|
|
55
55
|
}
|
|
56
|
-
recovery(presentationId) {
|
|
57
|
-
this.requireCurrent(presentationId);
|
|
58
|
-
if (!this.snapshot.appReady)
|
|
59
|
-
this.snapshot.phase = 'recovery';
|
|
60
|
-
return this.current();
|
|
61
|
-
}
|
|
62
56
|
hidden(presentationId) {
|
|
63
57
|
this.requireCurrent(presentationId);
|
|
64
58
|
this.snapshot = {
|
|
@@ -85,15 +79,15 @@ class SplashStateMachine {
|
|
|
85
79
|
}
|
|
86
80
|
}
|
|
87
81
|
|
|
88
|
-
const RECOVERY_TIMEOUT_MS = 15_000;
|
|
89
82
|
const PROGRESS_DELAY_MS = 1_000;
|
|
83
|
+
const COVER_FADE_MS = 500;
|
|
90
84
|
class LottieSplashWeb extends core.WebPlugin {
|
|
91
85
|
stateMachine = new SplashStateMachine();
|
|
92
86
|
playbackPromise;
|
|
93
87
|
resolvePlayback;
|
|
94
88
|
overlay;
|
|
95
89
|
progressTimer;
|
|
96
|
-
|
|
90
|
+
playbackTimer;
|
|
97
91
|
async show(options) {
|
|
98
92
|
const state = this.stateMachine.current();
|
|
99
93
|
if (state.visible && state.presentationId)
|
|
@@ -117,7 +111,7 @@ class LottieSplashWeb extends core.WebPlugin {
|
|
|
117
111
|
this.emitState();
|
|
118
112
|
if (!state.playbackSettled)
|
|
119
113
|
await this.waitForCompletion(options);
|
|
120
|
-
this.removeOverlay(options.presentationId);
|
|
114
|
+
await this.removeOverlay(options.presentationId);
|
|
121
115
|
}
|
|
122
116
|
async getState() {
|
|
123
117
|
const { generation: _generation, ...state } = this.stateMachine.current();
|
|
@@ -134,10 +128,14 @@ class LottieSplashWeb extends core.WebPlugin {
|
|
|
134
128
|
overlay.dataset.presentationId = presentationId;
|
|
135
129
|
Object.assign(overlay.style, {
|
|
136
130
|
position: 'fixed', inset: '0', zIndex: '2147483647', display: 'grid', placeItems: 'center',
|
|
137
|
-
background: this.backgroundFor(this.stateMachine.current().theme), overflow: 'hidden'
|
|
131
|
+
background: this.backgroundFor(this.stateMachine.current().theme), overflow: 'hidden',
|
|
132
|
+
opacity: '0', transition: `opacity ${COVER_FADE_MS}ms ease-in-out`
|
|
138
133
|
});
|
|
139
134
|
document.body.append(overlay);
|
|
140
135
|
this.overlay = overlay;
|
|
136
|
+
// Force the initial transparent style to paint before beginning the fade.
|
|
137
|
+
void overlay.offsetWidth;
|
|
138
|
+
overlay.style.opacity = '1';
|
|
141
139
|
// A generic package cannot ship HeadCount assets. The host supplies an approved local
|
|
142
140
|
// composition; absent or failed assets leave the opaque branded background in place.
|
|
143
141
|
if (!options.assets?.animationData && !options.assets?.animationPath) {
|
|
@@ -148,9 +146,13 @@ class LottieSplashWeb extends core.WebPlugin {
|
|
|
148
146
|
this.settle(presentationId, 'reduced-motion', 'Reduced motion is enabled');
|
|
149
147
|
return;
|
|
150
148
|
}
|
|
151
|
-
this.
|
|
152
|
-
|
|
153
|
-
|
|
149
|
+
this.playbackTimer = window.setTimeout(() => {
|
|
150
|
+
if (this.stateMachine.current().presentationId !== presentationId || this.overlay !== overlay)
|
|
151
|
+
return;
|
|
152
|
+
this.stateMachine.playing(presentationId);
|
|
153
|
+
this.emitState();
|
|
154
|
+
void this.playComposition(options, presentationId);
|
|
155
|
+
}, COVER_FADE_MS);
|
|
154
156
|
}
|
|
155
157
|
async playComposition(options, presentationId) {
|
|
156
158
|
try {
|
|
@@ -189,7 +191,6 @@ class LottieSplashWeb extends core.WebPlugin {
|
|
|
189
191
|
this.emitState();
|
|
190
192
|
if (!state.appReady) {
|
|
191
193
|
this.progressTimer = window.setTimeout(() => this.showLoadingRail(presentationId), PROGRESS_DELAY_MS);
|
|
192
|
-
this.recoveryTimer = window.setTimeout(() => this.showRecovery(presentationId), RECOVERY_TIMEOUT_MS);
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
catch {
|
|
@@ -212,32 +213,15 @@ class LottieSplashWeb extends core.WebPlugin {
|
|
|
212
213
|
rail.append(segment);
|
|
213
214
|
this.overlay?.append(style, rail);
|
|
214
215
|
}
|
|
215
|
-
|
|
216
|
-
let state;
|
|
217
|
-
try {
|
|
218
|
-
state = this.stateMachine.recovery(presentationId);
|
|
219
|
-
}
|
|
220
|
-
catch {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
if (state.presentationId !== presentationId || state.appReady)
|
|
224
|
-
return;
|
|
225
|
-
this.overlay?.querySelector('[data-splash-loading-rail]')?.remove();
|
|
226
|
-
const recovery = document.createElement('div');
|
|
227
|
-
recovery.style.cssText = 'position:absolute;bottom:12%;display:grid;gap:12px;place-items:center;color:inherit;font:500 16px system-ui;';
|
|
228
|
-
recovery.textContent = 'Still starting…';
|
|
229
|
-
const retry = document.createElement('button');
|
|
230
|
-
retry.type = 'button';
|
|
231
|
-
retry.textContent = 'Try again';
|
|
232
|
-
retry.onclick = () => window.location.reload();
|
|
233
|
-
recovery.append(retry);
|
|
234
|
-
this.overlay?.append(recovery);
|
|
235
|
-
this.emitState();
|
|
236
|
-
}
|
|
237
|
-
removeOverlay(presentationId) {
|
|
216
|
+
async removeOverlay(presentationId) {
|
|
238
217
|
window.clearTimeout(this.progressTimer);
|
|
239
|
-
window.clearTimeout(this.
|
|
240
|
-
this.overlay
|
|
218
|
+
window.clearTimeout(this.playbackTimer);
|
|
219
|
+
const overlay = this.overlay;
|
|
220
|
+
if (overlay) {
|
|
221
|
+
overlay.style.opacity = '0';
|
|
222
|
+
await new Promise(resolve => window.setTimeout(resolve, COVER_FADE_MS));
|
|
223
|
+
overlay.remove();
|
|
224
|
+
}
|
|
241
225
|
this.overlay = undefined;
|
|
242
226
|
this.stateMachine.hidden(presentationId);
|
|
243
227
|
this.emitState();
|
package/dist/plugin.js
CHANGED
|
@@ -67,12 +67,6 @@ var capacitorLottieSplash = (function (exports, core) {
|
|
|
67
67
|
this.snapshot.phase = 'hiding';
|
|
68
68
|
return this.current();
|
|
69
69
|
}
|
|
70
|
-
recovery(presentationId) {
|
|
71
|
-
this.requireCurrent(presentationId);
|
|
72
|
-
if (!this.snapshot.appReady)
|
|
73
|
-
this.snapshot.phase = 'recovery';
|
|
74
|
-
return this.current();
|
|
75
|
-
}
|
|
76
70
|
hidden(presentationId) {
|
|
77
71
|
this.requireCurrent(presentationId);
|
|
78
72
|
this.snapshot = {
|
|
@@ -99,15 +93,15 @@ var capacitorLottieSplash = (function (exports, core) {
|
|
|
99
93
|
}
|
|
100
94
|
}
|
|
101
95
|
|
|
102
|
-
const RECOVERY_TIMEOUT_MS = 15_000;
|
|
103
96
|
const PROGRESS_DELAY_MS = 1_000;
|
|
97
|
+
const COVER_FADE_MS = 500;
|
|
104
98
|
class LottieSplashWeb extends core.WebPlugin {
|
|
105
99
|
stateMachine = new SplashStateMachine();
|
|
106
100
|
playbackPromise;
|
|
107
101
|
resolvePlayback;
|
|
108
102
|
overlay;
|
|
109
103
|
progressTimer;
|
|
110
|
-
|
|
104
|
+
playbackTimer;
|
|
111
105
|
async show(options) {
|
|
112
106
|
const state = this.stateMachine.current();
|
|
113
107
|
if (state.visible && state.presentationId)
|
|
@@ -131,7 +125,7 @@ var capacitorLottieSplash = (function (exports, core) {
|
|
|
131
125
|
this.emitState();
|
|
132
126
|
if (!state.playbackSettled)
|
|
133
127
|
await this.waitForCompletion(options);
|
|
134
|
-
this.removeOverlay(options.presentationId);
|
|
128
|
+
await this.removeOverlay(options.presentationId);
|
|
135
129
|
}
|
|
136
130
|
async getState() {
|
|
137
131
|
const { generation: _generation, ...state } = this.stateMachine.current();
|
|
@@ -148,10 +142,14 @@ var capacitorLottieSplash = (function (exports, core) {
|
|
|
148
142
|
overlay.dataset.presentationId = presentationId;
|
|
149
143
|
Object.assign(overlay.style, {
|
|
150
144
|
position: 'fixed', inset: '0', zIndex: '2147483647', display: 'grid', placeItems: 'center',
|
|
151
|
-
background: this.backgroundFor(this.stateMachine.current().theme), overflow: 'hidden'
|
|
145
|
+
background: this.backgroundFor(this.stateMachine.current().theme), overflow: 'hidden',
|
|
146
|
+
opacity: '0', transition: `opacity ${COVER_FADE_MS}ms ease-in-out`
|
|
152
147
|
});
|
|
153
148
|
document.body.append(overlay);
|
|
154
149
|
this.overlay = overlay;
|
|
150
|
+
// Force the initial transparent style to paint before beginning the fade.
|
|
151
|
+
void overlay.offsetWidth;
|
|
152
|
+
overlay.style.opacity = '1';
|
|
155
153
|
// A generic package cannot ship HeadCount assets. The host supplies an approved local
|
|
156
154
|
// composition; absent or failed assets leave the opaque branded background in place.
|
|
157
155
|
if (!options.assets?.animationData && !options.assets?.animationPath) {
|
|
@@ -162,9 +160,13 @@ var capacitorLottieSplash = (function (exports, core) {
|
|
|
162
160
|
this.settle(presentationId, 'reduced-motion', 'Reduced motion is enabled');
|
|
163
161
|
return;
|
|
164
162
|
}
|
|
165
|
-
this.
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
this.playbackTimer = window.setTimeout(() => {
|
|
164
|
+
if (this.stateMachine.current().presentationId !== presentationId || this.overlay !== overlay)
|
|
165
|
+
return;
|
|
166
|
+
this.stateMachine.playing(presentationId);
|
|
167
|
+
this.emitState();
|
|
168
|
+
void this.playComposition(options, presentationId);
|
|
169
|
+
}, COVER_FADE_MS);
|
|
168
170
|
}
|
|
169
171
|
async playComposition(options, presentationId) {
|
|
170
172
|
try {
|
|
@@ -203,7 +205,6 @@ var capacitorLottieSplash = (function (exports, core) {
|
|
|
203
205
|
this.emitState();
|
|
204
206
|
if (!state.appReady) {
|
|
205
207
|
this.progressTimer = window.setTimeout(() => this.showLoadingRail(presentationId), PROGRESS_DELAY_MS);
|
|
206
|
-
this.recoveryTimer = window.setTimeout(() => this.showRecovery(presentationId), RECOVERY_TIMEOUT_MS);
|
|
207
208
|
}
|
|
208
209
|
}
|
|
209
210
|
catch {
|
|
@@ -226,32 +227,15 @@ var capacitorLottieSplash = (function (exports, core) {
|
|
|
226
227
|
rail.append(segment);
|
|
227
228
|
this.overlay?.append(style, rail);
|
|
228
229
|
}
|
|
229
|
-
|
|
230
|
-
let state;
|
|
231
|
-
try {
|
|
232
|
-
state = this.stateMachine.recovery(presentationId);
|
|
233
|
-
}
|
|
234
|
-
catch {
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
if (state.presentationId !== presentationId || state.appReady)
|
|
238
|
-
return;
|
|
239
|
-
this.overlay?.querySelector('[data-splash-loading-rail]')?.remove();
|
|
240
|
-
const recovery = document.createElement('div');
|
|
241
|
-
recovery.style.cssText = 'position:absolute;bottom:12%;display:grid;gap:12px;place-items:center;color:inherit;font:500 16px system-ui;';
|
|
242
|
-
recovery.textContent = 'Still starting…';
|
|
243
|
-
const retry = document.createElement('button');
|
|
244
|
-
retry.type = 'button';
|
|
245
|
-
retry.textContent = 'Try again';
|
|
246
|
-
retry.onclick = () => window.location.reload();
|
|
247
|
-
recovery.append(retry);
|
|
248
|
-
this.overlay?.append(recovery);
|
|
249
|
-
this.emitState();
|
|
250
|
-
}
|
|
251
|
-
removeOverlay(presentationId) {
|
|
230
|
+
async removeOverlay(presentationId) {
|
|
252
231
|
window.clearTimeout(this.progressTimer);
|
|
253
|
-
window.clearTimeout(this.
|
|
254
|
-
this.overlay
|
|
232
|
+
window.clearTimeout(this.playbackTimer);
|
|
233
|
+
const overlay = this.overlay;
|
|
234
|
+
if (overlay) {
|
|
235
|
+
overlay.style.opacity = '0';
|
|
236
|
+
await new Promise(resolve => window.setTimeout(resolve, COVER_FADE_MS));
|
|
237
|
+
overlay.remove();
|
|
238
|
+
}
|
|
255
239
|
this.overlay = undefined;
|
|
256
240
|
this.stateMachine.hidden(presentationId);
|
|
257
241
|
this.emitState();
|
|
@@ -149,7 +149,7 @@ public struct LottieSplashConfiguration {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
public enum LottieSplashPhase: String {
|
|
152
|
-
case preparing, playing, holding, hiding, hidden
|
|
152
|
+
case preparing, playing, holding, hiding, hidden
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
public final class LottieSplashCoordinator {
|
|
@@ -159,7 +159,7 @@ public final class LottieSplashCoordinator {
|
|
|
159
159
|
/// permanently obscure that bundle during an over-the-air/native rollout mismatch.
|
|
160
160
|
private let webAdoptionGracePeriod: TimeInterval = 10
|
|
161
161
|
private let progressDelay: TimeInterval = 1
|
|
162
|
-
private let
|
|
162
|
+
private let coverFadeDuration: TimeInterval = 0.5
|
|
163
163
|
|
|
164
164
|
public private(set) var presentationId: String?
|
|
165
165
|
public private(set) var phase: LottieSplashPhase = .hidden
|
|
@@ -167,20 +167,18 @@ public final class LottieSplashCoordinator {
|
|
|
167
167
|
public private(set) var appReady = false
|
|
168
168
|
public private(set) var theme: LottieSplashTheme = .dark
|
|
169
169
|
public var onStateChange: ((LottieSplashCoordinator) -> Void)?
|
|
170
|
-
public var onRecoveryRequested: (() -> Void)?
|
|
171
170
|
|
|
172
171
|
private weak var hostController: UIViewController?
|
|
173
172
|
private var cover: LottieSplashCoverView?
|
|
174
173
|
private var animationWebView: WKWebView?
|
|
175
174
|
private var animationMessageBridge: LottieSplashMessageBridge?
|
|
176
175
|
private var loadingRail: LottieSplashLoadingRail?
|
|
177
|
-
private var recoveryWorkItem: DispatchWorkItem?
|
|
178
176
|
private var progressWorkItem: DispatchWorkItem?
|
|
179
177
|
private var adoptionWorkItem: DispatchWorkItem?
|
|
180
178
|
private var adoptedByWeb = false
|
|
181
179
|
private var webRendererLoaded = false
|
|
180
|
+
private var coverFadeInComplete = false
|
|
182
181
|
private var progressRemaining = TimeInterval(1)
|
|
183
|
-
private var recoveryRemaining = TimeInterval(15)
|
|
184
182
|
private var waitingClockStartedAt: Date?
|
|
185
183
|
private var playbackCompletion: ((String, String?) -> Void)?
|
|
186
184
|
private let queue = DispatchQueue.main
|
|
@@ -243,7 +241,7 @@ public final class LottieSplashCoordinator {
|
|
|
243
241
|
}
|
|
244
242
|
|
|
245
243
|
public func startPlaybackIfVisible(configuration: LottieSplashConfiguration = .init()) {
|
|
246
|
-
guard phase == .preparing, animationWebView != nil else { return }
|
|
244
|
+
guard phase == .preparing, animationWebView != nil, coverFadeInComplete else { return }
|
|
247
245
|
phase = .playing
|
|
248
246
|
emitState()
|
|
249
247
|
guard !UIAccessibility.isReduceMotionEnabled else {
|
|
@@ -296,6 +294,7 @@ public final class LottieSplashCoordinator {
|
|
|
296
294
|
|
|
297
295
|
private func buildCover(in controller: UIViewController, configuration: LottieSplashConfiguration) {
|
|
298
296
|
let cover = LottieSplashCoverView(theme: configuration.resolvedTheme)
|
|
297
|
+
cover.alpha = 0
|
|
299
298
|
cover.translatesAutoresizingMaskIntoConstraints = false
|
|
300
299
|
cover.isAccessibilityElement = true
|
|
301
300
|
cover.accessibilityLabel = "Loading application"
|
|
@@ -334,6 +333,15 @@ public final class LottieSplashCoordinator {
|
|
|
334
333
|
self.animationWebView = animation
|
|
335
334
|
self.animationMessageBridge = messageBridge
|
|
336
335
|
self.webRendererLoaded = false
|
|
336
|
+
self.coverFadeInComplete = false
|
|
337
|
+
controller.view.layoutIfNeeded()
|
|
338
|
+
UIView.animate(withDuration: UIAccessibility.isReduceMotionEnabled ? 0 : coverFadeDuration, delay: 0, options: [.curveEaseInOut]) {
|
|
339
|
+
cover.alpha = 1
|
|
340
|
+
} completion: { [weak self, weak cover] _ in
|
|
341
|
+
guard let self, let cover, self.cover === cover else { return }
|
|
342
|
+
self.coverFadeInComplete = true
|
|
343
|
+
self.startPlaybackIfVisible()
|
|
344
|
+
}
|
|
337
345
|
}
|
|
338
346
|
|
|
339
347
|
private func apply(theme: LottieSplashTheme) {
|
|
@@ -401,20 +409,13 @@ public final class LottieSplashCoordinator {
|
|
|
401
409
|
])
|
|
402
410
|
self.loadingRail = rail
|
|
403
411
|
}
|
|
404
|
-
let recovery = DispatchWorkItem { [weak self] in
|
|
405
|
-
self?.recoveryRemaining = 0
|
|
406
|
-
self?.showRecovery(for: id)
|
|
407
|
-
}
|
|
408
412
|
progressWorkItem = progress
|
|
409
|
-
recoveryWorkItem = recovery
|
|
410
413
|
if self.loadingRail == nil { queue.asyncAfter(deadline: .now() + progressRemaining, execute: progress) }
|
|
411
|
-
queue.asyncAfter(deadline: .now() + recoveryRemaining, execute: recovery)
|
|
412
414
|
}
|
|
413
415
|
|
|
414
416
|
private func resetWaitingIndicators(for id: String) {
|
|
415
417
|
cancelWaitingTasks()
|
|
416
418
|
progressRemaining = progressDelay
|
|
417
|
-
recoveryRemaining = recoveryDelay
|
|
418
419
|
scheduleWaitingIndicators(for: id)
|
|
419
420
|
}
|
|
420
421
|
|
|
@@ -422,15 +423,12 @@ public final class LottieSplashCoordinator {
|
|
|
422
423
|
guard let waitingClockStartedAt else { return }
|
|
423
424
|
let elapsed = Date().timeIntervalSince(waitingClockStartedAt)
|
|
424
425
|
progressRemaining = max(0, progressRemaining - elapsed)
|
|
425
|
-
recoveryRemaining = max(0, recoveryRemaining - elapsed)
|
|
426
426
|
cancelWaitingTasks()
|
|
427
427
|
}
|
|
428
428
|
|
|
429
429
|
private func cancelWaitingTasks() {
|
|
430
430
|
progressWorkItem?.cancel()
|
|
431
|
-
recoveryWorkItem?.cancel()
|
|
432
431
|
progressWorkItem = nil
|
|
433
|
-
recoveryWorkItem = nil
|
|
434
432
|
waitingClockStartedAt = nil
|
|
435
433
|
}
|
|
436
434
|
|
|
@@ -445,33 +443,6 @@ public final class LottieSplashCoordinator {
|
|
|
445
443
|
queue.asyncAfter(deadline: .now() + webAdoptionGracePeriod, execute: fallback)
|
|
446
444
|
}
|
|
447
445
|
|
|
448
|
-
private func showRecovery(for id: String) {
|
|
449
|
-
guard presentationId == id, !appReady else { return }
|
|
450
|
-
cancelWaitingTasks()
|
|
451
|
-
phase = .recovery
|
|
452
|
-
loadingRail?.removeFromSuperview()
|
|
453
|
-
loadingRail = nil
|
|
454
|
-
let stack = UIStackView()
|
|
455
|
-
stack.axis = .vertical
|
|
456
|
-
stack.spacing = 12
|
|
457
|
-
stack.alignment = .center
|
|
458
|
-
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
459
|
-
let label = UILabel()
|
|
460
|
-
label.text = "Still starting…"
|
|
461
|
-
label.accessibilityTraits = .updatesFrequently
|
|
462
|
-
let retry = UIButton(type: .system)
|
|
463
|
-
retry.setTitle("Try again", for: .normal)
|
|
464
|
-
retry.addAction(UIAction { [weak self] _ in self?.onRecoveryRequested?() }, for: .primaryActionTriggered)
|
|
465
|
-
stack.addArrangedSubview(label)
|
|
466
|
-
stack.addArrangedSubview(retry)
|
|
467
|
-
cover?.addSubview(stack)
|
|
468
|
-
if let cover {
|
|
469
|
-
NSLayoutConstraint.activate([stack.centerXAnchor.constraint(equalTo: cover.centerXAnchor), stack.bottomAnchor.constraint(equalTo: cover.safeAreaLayoutGuide.bottomAnchor, constant: -48)])
|
|
470
|
-
}
|
|
471
|
-
UIAccessibility.post(notification: .announcement, argument: "Still starting")
|
|
472
|
-
emitState()
|
|
473
|
-
}
|
|
474
|
-
|
|
475
446
|
private func dismiss(completion: @escaping (Error?) -> Void) {
|
|
476
447
|
guard phase != .hidden, let cover, let id = presentationId else { completion(nil); return }
|
|
477
448
|
phase = .hiding
|
|
@@ -480,7 +451,7 @@ public final class LottieSplashCoordinator {
|
|
|
480
451
|
adoptionWorkItem = nil
|
|
481
452
|
loadingRail?.removeFromSuperview()
|
|
482
453
|
loadingRail = nil
|
|
483
|
-
UIView.animate(withDuration: UIAccessibility.isReduceMotionEnabled ? 0 :
|
|
454
|
+
UIView.animate(withDuration: UIAccessibility.isReduceMotionEnabled ? 0 : coverFadeDuration, animations: { cover.alpha = 0 }) { [weak self] _ in
|
|
484
455
|
cover.removeFromSuperview()
|
|
485
456
|
self?.cover = nil
|
|
486
457
|
self?.phase = .hidden
|
|
@@ -488,6 +459,7 @@ public final class LottieSplashCoordinator {
|
|
|
488
459
|
self?.animationWebView?.configuration.userContentController.removeScriptMessageHandler(forName: "lottieSplash")
|
|
489
460
|
self?.animationWebView = nil
|
|
490
461
|
self?.animationMessageBridge = nil
|
|
462
|
+
self?.coverFadeInComplete = false
|
|
491
463
|
self?.emitState()
|
|
492
464
|
_ = id
|
|
493
465
|
completion(nil)
|
|
@@ -17,7 +17,6 @@ public class LottieSplashPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
17
17
|
|
|
18
18
|
override public func load() {
|
|
19
19
|
coordinator.onStateChange = { [weak self] coordinator in self?.notifyListeners("stateChange", data: self?.stateData(coordinator) ?? [:]) }
|
|
20
|
-
coordinator.onRecoveryRequested = { [weak self] in self?.bridge?.webView?.reload() }
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
@objc public func show(_ call: CAPPluginCall) {
|