execute method

Future<void> execute({
  1. required void onProgress(
    1. int completed,
    2. int total,
    3. String label
    ),
  2. void onSubProgress(
    1. double globalFraction
    )?,
})

Executes all tasks in phase order, invoking onProgress before each one and once more with label 'startup.ready' when all tasks finish.

onProgress receives the number of already-completed tasks, the total task count, and the label of the task that is about to run. This means the splash screen always shows the currently-running task name, and the progress percentage reflects work already done before the current task started.

The optional onSubProgress receives a global progress fraction (0.0–1.0) when the active task reports intra-task progress. It is only called when the running task invokes its own onSubProgress callback; tasks that do not implement sub-progress never trigger it.

Throws JailbreakAppException if a jailbreak is detected — this is a terminal condition and the app should not continue. All other exceptions are logged before being re-thrown.

Implementation

Future<void> execute({
  required void Function(int completed, int total, String label) onProgress,
  void Function(double globalFraction)? onSubProgress,
}) async {
  final int total = _tasks.length;
  int completed = 0;

  _logger.info('[StartupOrchestrator] Starting $total tasks');

  for (final StartupPhase phase in StartupPhase.values) {
    final List<StartupTask> phaseTasks = _tasks.where((StartupTask t) => t.phase == phase).toList();
    if (phaseTasks.isEmpty) continue;

    _logger.info('[StartupOrchestrator] Phase: ${phase.name}');

    for (final StartupTask task in phaseTasks) {
      _logger.info('[StartupOrchestrator] Running: ${task.id.name}');

      // Announce the task BEFORE executing so the label shows what is
      // currently running rather than what just finished.
      onProgress(completed, total, task.label);

      try {
        final sw = Stopwatch()..start();
        await task.execute(
          onSubProgress: onSubProgress == null ? null : (double subFrac) => onSubProgress((completed + subFrac.clamp(0.0, 1.0)) / total),
        );
        sw.stop();
        Sentry.metrics.distribution('startup.task.${task.id.name}_ms', sw.elapsedMilliseconds.toDouble(), unit: 'millisecond');
        Sentry.metrics.count('startup.task.completed', 1);
        completed++;
      } on JailbreakAppException {
        rethrow; // Terminal security condition — cubit handles it.
      } on ForceUpdateException {
        rethrow; // Expected product gate — cubit handles it, not an error.
      } catch (e, st) {
        _logger.error('[StartupOrchestrator] Task ${task.id.name} failed', error: e, stackTrace: st);
        Sentry.metrics.count('startup.task.failed', 1);
        rethrow;
      }
    }
  }

  onProgress(total, total, Strings.startupReady);
  _logger.info('[StartupOrchestrator] All tasks completed');
}