start method

Future<void> start()

Begins the startup sequence.

A call while already StartupStatus.running is a no-op. On success, emits StartupStatus.completed. On jailbreak, emits StartupStatus.jailbreakDetected with the exception. When the installed version is below the remote-config minimum, emits StartupStatus.forceUpdateRequired. On any other failure, emits StartupStatus.failed with a diagnostic message.

Implementation

Future<void> start() async {
  if (state.status == StartupStatus.running) return;

  emit(const StartupState(status: StartupStatus.running));
  final sw = Stopwatch()..start();

  try {
    await _orchestrator.execute(
      onProgress: (int completed, int total, String label) {
        // Construct a fresh state to guarantee subTaskFraction is cleared at
        // every task boundary rather than relying on copyWith ?? semantics.
        // Guarded: this callback can fire after the cubit is closed (e.g.
        // the splash screen is disposed mid-task), which would otherwise
        // throw a StateError on emit().
        if (!isClosed) emit(StartupState(status: StartupStatus.running, completedCount: completed, totalCount: total, currentLabel: label));
      },
      onSubProgress: (double globalFraction) {
        if (!isClosed) emit(state.copyWith(subTaskFraction: globalFraction));
      },
    );

    // Allow the splash progress animation to visually reach 100% before
    // emitting completed, which triggers GoRouter to navigate away.
    // The 200 ms buffer on top of the animation duration absorbs any frame jitter.
    await Future<void>.delayed(_progressAnimationDuration + const Duration(milliseconds: 200));
    sw.stop();
    Sentry.metrics.distribution('startup.total_ms', sw.elapsedMilliseconds.toDouble(), unit: 'millisecond');
    Sentry.metrics.count('startup.completed', 1);
    if (!isClosed) emit(state.copyWith(status: StartupStatus.completed));
  } on JailbreakAppException catch (e) {
    _logger.warn('[StartupCubit] Jailbreak detected', error: e);
    Sentry.metrics.count('startup.jailbreak_detected', 1);
    if (!isClosed) emit(state.copyWith(status: StartupStatus.jailbreakDetected, jailbreakException: e));
  } on ForceUpdateException catch (e) {
    _logger.warn('[StartupCubit] Force update required — ${e.message}');
    Sentry.metrics.count('startup.force_update_required', 1);
    if (!isClosed) emit(state.copyWith(status: StartupStatus.forceUpdateRequired, forceUpdateException: e));
  } catch (e, st) {
    _logger.error('[StartupCubit] Startup failed', error: e, stackTrace: st);
    Sentry.metrics.count('startup.failed', 1);
    if (!isClosed) emit(state.copyWith(status: StartupStatus.failed, errorMessage: e.toString()));
  }
}