capacitor-lottie-splash 0.1.1 → 0.1.3

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 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. After 15 foreground seconds the rail stops and a native/browser recovery surface presents “Still starting…” and “Try again.” The native retry reloads the WebView, so it does not depend on JavaScript having already loaded. Reduced-motion and asset failures retain the theme-matched native cover instead of exposing a blank screen.
104
+ 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; a broken boot transitions to the visible recovery surface instead of exposing a blank page.
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, RECOVERY }
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,6 @@ 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 RECOVERY_DELAY_MS = 15_000L
86
82
  private const val COVER_FADE_OUT_MS = 500L
87
83
  private val main = Handler(Looper.getMainLooper())
88
84
 
@@ -94,8 +90,6 @@ object LottieSplashCoordinator {
94
90
  private var compositionReady = false
95
91
  private var requestedPlayback = AndroidSplashPlayback.PLAY
96
92
  private var loadingRail: LottieSplashLoadingRail? = null
97
- private var recoveryMessage: TextView? = null
98
- private var recoveryRetry: Button? = null
99
93
  private var presentationId: String? = null
100
94
  private var phase = AndroidSplashPhase.HIDDEN
101
95
  private var playbackSettled = false
@@ -106,16 +100,13 @@ object LottieSplashCoordinator {
106
100
  private var completion: ((String, String?) -> Unit)? = null
107
101
  private var pendingHide: ((Throwable?) -> Unit)? = null
108
102
  private var progressTask: Runnable? = null
109
- private var recoveryTask: Runnable? = null
110
103
  private var adoptionTask: Runnable? = null
111
104
  private var adoptedByWeb = false
112
105
  private var progressRemainingMs = PROGRESS_DELAY_MS
113
- private var recoveryRemainingMs = RECOVERY_DELAY_MS
114
106
  private var waitingClockStartedAtMs = 0L
115
107
  private var waitingClockRunning = false
116
108
 
117
109
  var onStateChange: (() -> Unit)? = null
118
- var onRecoveryRequested: (() -> Unit)? = null
119
110
 
120
111
  /**
121
112
  * Called by AndroidX while deciding whether its system-owned launch surface may leave.
@@ -153,8 +144,6 @@ object LottieSplashCoordinator {
153
144
  playbackOutcome = "static-fallback"
154
145
  playbackReason = null
155
146
  loadingRail = null
156
- recoveryMessage = null
157
- recoveryRetry = null
158
147
 
159
148
  val backgroundStart = if (darkTheme) Color.rgb(30, 30, 30) else Color.rgb(240, 240, 240)
160
149
  root.setBackgroundColor(backgroundStart)
@@ -354,7 +343,6 @@ object LottieSplashCoordinator {
354
343
  private fun resetWaitingUi(id: String) {
355
344
  cancelWaitingTasks()
356
345
  progressRemainingMs = PROGRESS_DELAY_MS
357
- recoveryRemainingMs = RECOVERY_DELAY_MS
358
346
  scheduleWaitingUi(id)
359
347
  }
360
348
 
@@ -371,24 +359,19 @@ object LottieSplashCoordinator {
371
359
  waitingClockRunning = true
372
360
  waitingClockStartedAtMs = SystemClock.elapsedRealtime()
373
361
  progressTask = Runnable { showLoadingRail(id) }
374
- recoveryTask = Runnable { showRecovery(id) }
375
362
  if (loadingRail == null) main.postDelayed(progressTask!!, progressRemainingMs)
376
- main.postDelayed(recoveryTask!!, recoveryRemainingMs)
377
363
  }
378
364
 
379
365
  private fun pauseWaitingClock() {
380
366
  if (!waitingClockRunning) return
381
367
  val elapsed = SystemClock.elapsedRealtime() - waitingClockStartedAtMs
382
368
  progressRemainingMs = max(0, progressRemainingMs - elapsed)
383
- recoveryRemainingMs = max(0, recoveryRemainingMs - elapsed)
384
369
  cancelWaitingTasks()
385
370
  }
386
371
 
387
372
  private fun cancelWaitingTasks() {
388
373
  progressTask?.let(main::removeCallbacks)
389
- recoveryTask?.let(main::removeCallbacks)
390
374
  progressTask = null
391
- recoveryTask = null
392
375
  waitingClockRunning = false
393
376
  }
394
377
 
@@ -404,36 +387,10 @@ object LottieSplashCoordinator {
404
387
  }
405
388
  }
406
389
 
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
390
  private fun clearWaitingUi() {
431
391
  cancelWaitingTasks()
432
392
  loadingRail?.let { cover?.removeView(it) }
433
- recoveryMessage?.parent?.let { parent -> (parent as? ViewGroup)?.let { cover?.removeView(it) } }
434
393
  loadingRail = null
435
- recoveryMessage = null
436
- recoveryRetry = null
437
394
  }
438
395
 
439
396
  private fun dismiss(callback: (Throwable?) -> Unit) {
@@ -464,8 +421,6 @@ object LottieSplashCoordinator {
464
421
  animation = null
465
422
  hostActivity = null
466
423
  loadingRail = null
467
- recoveryMessage = null
468
- recoveryRetry = null
469
424
  presentationId = null
470
425
  phase = AndroidSplashPhase.HIDDEN
471
426
  compositionReady = false
@@ -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 in recovery.
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' | 'recovery';
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,6 @@ export declare class LottieSplashWeb extends WebPlugin implements LottieSplashPl
6
6
  private resolvePlayback?;
7
7
  private overlay?;
8
8
  private progressTimer?;
9
- private recoveryTimer?;
10
9
  show(options: SplashShowOptions): Promise<Presentation>;
11
10
  setLaunchTheme(_options: SplashLaunchThemeOptions): Promise<void>;
12
11
  waitForCompletion(options: {
@@ -21,7 +20,6 @@ export declare class LottieSplashWeb extends WebPlugin implements LottieSplashPl
21
20
  private playComposition;
22
21
  private settle;
23
22
  private showLoadingRail;
24
- private showRecovery;
25
23
  private removeOverlay;
26
24
  private ensureCurrent;
27
25
  private emitState;
package/dist/esm/web.js CHANGED
@@ -1,6 +1,5 @@
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;
5
4
  export class LottieSplashWeb extends WebPlugin {
6
5
  stateMachine = new SplashStateMachine();
@@ -8,7 +7,6 @@ export class LottieSplashWeb extends WebPlugin {
8
7
  resolvePlayback;
9
8
  overlay;
10
9
  progressTimer;
11
- recoveryTimer;
12
10
  async show(options) {
13
11
  const state = this.stateMachine.current();
14
12
  if (state.visible && state.presentationId)
@@ -104,7 +102,6 @@ export class LottieSplashWeb extends WebPlugin {
104
102
  this.emitState();
105
103
  if (!state.appReady) {
106
104
  this.progressTimer = window.setTimeout(() => this.showLoadingRail(presentationId), PROGRESS_DELAY_MS);
107
- this.recoveryTimer = window.setTimeout(() => this.showRecovery(presentationId), RECOVERY_TIMEOUT_MS);
108
105
  }
109
106
  }
110
107
  catch {
@@ -127,31 +124,8 @@ export class LottieSplashWeb extends WebPlugin {
127
124
  rail.append(segment);
128
125
  this.overlay?.append(style, rail);
129
126
  }
130
- showRecovery(presentationId) {
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
127
  removeOverlay(presentationId) {
153
128
  window.clearTimeout(this.progressTimer);
154
- window.clearTimeout(this.recoveryTimer);
155
129
  this.overlay?.remove();
156
130
  this.overlay = undefined;
157
131
  this.stateMachine.hidden(presentationId);