shuffle the running-verb order per turn (T-255)
Each RunningIndicator shuffles a copy of the verb list on creation, so a turn doesn't always start with the same word. A `shuffle` flag (default true) is turned off in tests for deterministic assertions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,13 +41,21 @@ const List<String> runningVerbs = [
|
|||||||
const int _secondsPerWord = 4;
|
const int _secondsPerWord = 4;
|
||||||
|
|
||||||
class RunningIndicator extends StatefulWidget {
|
class RunningIndicator extends StatefulWidget {
|
||||||
const RunningIndicator({super.key});
|
const RunningIndicator({super.key, this.shuffle = true});
|
||||||
|
|
||||||
|
/// Randomize the rotation order per turn so you don't always see the same
|
||||||
|
/// sequence. Off in tests for deterministic assertions.
|
||||||
|
final bool shuffle;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RunningIndicator> createState() => _RunningIndicatorState();
|
State<RunningIndicator> createState() => _RunningIndicatorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RunningIndicatorState extends State<RunningIndicator> with SingleTickerProviderStateMixin {
|
class _RunningIndicatorState extends State<RunningIndicator> with SingleTickerProviderStateMixin {
|
||||||
|
/// The verbs for this turn — a shuffled copy in production, the canonical
|
||||||
|
/// order in tests. Same length, so the period is unchanged.
|
||||||
|
late final List<String> _verbs = widget.shuffle ? (List<String>.of(runningVerbs)..shuffle()) : runningVerbs;
|
||||||
|
|
||||||
// One full pass over every verb; value 0→1 maps linearly to elapsed seconds.
|
// One full pass over every verb; value 0→1 maps linearly to elapsed seconds.
|
||||||
static final int _periodSeconds = runningVerbs.length * _secondsPerWord;
|
static final int _periodSeconds = runningVerbs.length * _secondsPerWord;
|
||||||
|
|
||||||
@@ -81,13 +89,13 @@ class _RunningIndicatorState extends State<RunningIndicator> with SingleTickerPr
|
|||||||
label: 'Claude is running',
|
label: 'Claude is running',
|
||||||
child: ExcludeSemantics(
|
child: ExcludeSemantics(
|
||||||
child: reduced
|
child: reduced
|
||||||
? ClideText('${runningVerbs.first}…', muted: true, fontSize: clideFontMeta)
|
? ClideText('${_verbs.first}…', muted: true, fontSize: clideFontMeta)
|
||||||
: AnimatedBuilder(
|
: AnimatedBuilder(
|
||||||
animation: _c,
|
animation: _c,
|
||||||
builder: (ctx, _) {
|
builder: (ctx, _) {
|
||||||
final elapsed = _c.value * _periodSeconds;
|
final elapsed = _c.value * _periodSeconds;
|
||||||
final dots = '.' * (elapsed.floor() % 4);
|
final dots = '.' * (elapsed.floor() % 4);
|
||||||
final word = runningVerbs[(elapsed ~/ _secondsPerWord) % runningVerbs.length];
|
final word = _verbs[(elapsed ~/ _secondsPerWord) % _verbs.length];
|
||||||
return ClideText('$word$dots', muted: true, fontSize: clideFontMeta);
|
return ClideText('$word$dots', muted: true, fontSize: clideFontMeta);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ void main() {
|
|||||||
|
|
||||||
Widget wrap({bool reducedMotion = false}) => MediaQuery(
|
Widget wrap({bool reducedMotion = false}) => MediaQuery(
|
||||||
data: MediaQueryData(disableAnimations: reducedMotion),
|
data: MediaQueryData(disableAnimations: reducedMotion),
|
||||||
child: const RunningIndicator(),
|
child: const RunningIndicator(shuffle: false),
|
||||||
);
|
);
|
||||||
|
|
||||||
testWidgets('animates the ellipsis and rotates the verb', (tester) async {
|
testWidgets('animates the ellipsis and rotates the verb', (tester) async {
|
||||||
@@ -51,4 +51,12 @@ void main() {
|
|||||||
await tester.pump(const Duration(seconds: 6));
|
await tester.pump(const Duration(seconds: 6));
|
||||||
expect(_text(tester), 'Pondering…'); // unchanged — no animation running
|
expect(_text(tester), 'Pondering…'); // unchanged — no animation running
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('shuffle:true renders a verb from the list', (tester) async {
|
||||||
|
await tester.pumpWidget(harness(f, const MediaQuery(data: MediaQueryData(), child: RunningIndicator(shuffle: true))));
|
||||||
|
await tester.pump();
|
||||||
|
final word = _text(tester)!.replaceAll('.', '');
|
||||||
|
expect(runningVerbs.contains(word), isTrue);
|
||||||
|
await tester.pumpWidget(harness(f, const SizedBox())); // dispose the animation
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user