import 'dart:async'; enum StepState { pending, active, done, failed, skipped } class DeployStep { final String key; final String label; StepState state; double? progress; // 0..1 within this step, when meaningful DeployStep( this.key, this.label, { this.state = StepState.pending, this.progress, }); } /// Tracks an ordered list of steps for a build or deploy operation and streams /// updates so the UI can render a live stepper with per-step progress. class StepTracker { final _controller = StreamController>.broadcast(); Stream> get stream => _controller.stream; final List steps = []; void reset(List<(String, String)> defs) { steps ..clear() ..addAll(defs.map((d) => DeployStep(d.$1, d.$2))); _emit(); } DeployStep? _find(String key) { for (final s in steps) { if (s.key == key) return s; } return null; } void active(String key) { final s = _find(key); if (s == null) return; // mark earlier active/pending steps done as we move forward for (final step in steps) { if (step == s) break; if (step.state == StepState.active) step.state = StepState.done; } s.state = StepState.active; s.progress = null; _emit(); } void progress(String key, double? p) { final s = _find(key); if (s == null) return; s.progress = p; _emit(); } void done(String key) => _set(key, StepState.done); void fail(String key) => _set(key, StepState.failed); void skip(String key) => _set(key, StepState.skipped); void _set(String key, StepState state) { final s = _find(key); if (s == null) return; s.state = state; s.progress = null; _emit(); } /// Marks the currently-active step done (used before moving on). void completeActive() { for (final s in steps) { if (s.state == StepState.active) s.state = StepState.done; } _emit(); } /// Any still-pending/active steps become failed (on an error). void failRemaining() { for (final s in steps) { if (s.state == StepState.active) { s.state = StepState.failed; } else if (s.state == StepState.pending) { s.state = StepState.skipped; } } _emit(); } void clear() { steps.clear(); _emit(); } void _emit() { if (!_controller.isClosed) { _controller.add(List.unmodifiable(steps)); } } void dispose() => _controller.close(); }