Files
clide/lib/widgets/src/clide_status_indicator.dart
T
jpmschweitzerandClaude Opus 4.8 6d0ebab721 chore: adopt Dart 3.9 toolchain — honest floor + tall-style reformat (T-353)
Raise the declared minimums in pubspec.yaml to what our deps already
require: Flutter >=3.35.0 / Dart >=3.9.0 (was 3.19.0 / 3.5.0). alchemist
0.12 needs Flutter 3.32; Dart 3.9 first ships in Flutter 3.35, so 3.35 is
the binding floor. Pin the exact build toolchain in .fvmrc (Flutter
3.44.1).

Moving to the Dart 3.9 language level switches `dart format` to the new
"tall" style and enables two new lints. This commit is the resulting
mechanical churn, isolated from any behaviour change:
  - whole-tree `dart format` reformat (tall style)
  - `dart fix` for unnecessary_underscores + use_null_aware_elements

No runtime behaviour change; `make test` green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 12:11:53 +02:00

62 lines
2.6 KiB
Dart

/// A self-contained run-status glyph: spinner while running, check on success,
/// cross on failure (T-296).
///
/// Deliberately NOT built on ConversationCard's success/error mark — it owns its
/// own states and rendering so the spinner→check / spinner→cross transition can
/// grow richer (a morph/cross-fade) without being constrained by that card. A
/// light [AnimatedSwitcher] cross-fade between states is wired now; the keyed
/// children leave the seam for a fuller transition later.
library;
import 'package:clide/kernel/src/theme/controller.dart';
import 'package:clide/widgets/src/clide_icon.dart';
import 'package:clide/widgets/src/clide_spinner.dart';
import 'package:clide/widgets/src/icons/check.dart';
import 'package:clide/widgets/src/icons/x.dart';
import 'package:flutter/widgets.dart';
enum ClideRunStatus { running, success, error }
class ClideStatusIndicator extends StatefulWidget {
const ClideStatusIndicator({super.key, required this.status, this.size = 14});
final ClideRunStatus status;
final double size;
@override
State<ClideStatusIndicator> createState() => _ClideStatusIndicatorState();
}
class _ClideStatusIndicatorState extends State<ClideStatusIndicator> {
/// Monotonic id bumped on every status change, folded into the child key. A
/// per-status-only key collides inside [AnimatedSwitcher]'s Stack when a
/// status re-appears (running → success → running within the cross-fade)
/// while its previous glyph is still animating out — the exiting and entering
/// children share `ValueKey('running')` and trip the duplicate-key assertion
/// (T-326). The sequence makes each appearance's key unique; a same-status
/// rebuild keeps the key, so it still doesn't re-animate.
int _seq = 0;
@override
void didUpdateWidget(ClideStatusIndicator old) {
super.didUpdateWidget(old);
if (old.status != widget.status) _seq++;
}
@override
Widget build(BuildContext context) {
final tokens = ClideTheme.of(context).surface;
final key = ValueKey('${widget.status.name}-$_seq');
final (Widget glyph, String label) = switch (widget.status) {
ClideRunStatus.running => (ClideSpinner(size: widget.size, color: tokens.globalTextMuted, key: key), 'running'),
ClideRunStatus.success => (ClideIcon(const CheckIcon(), size: widget.size, color: tokens.statusSuccess, key: key), 'succeeded'),
ClideRunStatus.error => (ClideIcon(const CloseIcon(), size: widget.size, color: tokens.statusError, key: key), 'failed'),
};
return Semantics(
label: label,
container: true,
child: AnimatedSwitcher(duration: const Duration(milliseconds: 200), child: glyph),
);
}
}