feat(core): let AppBanner disable its action instead of hiding it
Refs #1 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
86bcd25f30
commit
553a2b67cd
2 changed files with 42 additions and 1 deletions
|
|
@ -17,6 +17,7 @@ class AppBanner extends StatelessWidget {
|
||||||
super.key,
|
super.key,
|
||||||
this.actionLabel,
|
this.actionLabel,
|
||||||
this.onAction,
|
this.onAction,
|
||||||
|
this.actionEnabled = true,
|
||||||
}) : assert(
|
}) : assert(
|
||||||
(actionLabel == null) == (onAction == null),
|
(actionLabel == null) == (onAction == null),
|
||||||
'actionLabel and onAction belong together',
|
'actionLabel and onAction belong together',
|
||||||
|
|
@ -28,6 +29,11 @@ class AppBanner extends StatelessWidget {
|
||||||
final String? actionLabel;
|
final String? actionLabel;
|
||||||
final VoidCallback? onAction;
|
final VoidCallback? onAction;
|
||||||
|
|
||||||
|
/// `false` keeps the action visible and in the focus order but disabled
|
||||||
|
/// (e.g. while a request runs). Screen readers still find it and hear that
|
||||||
|
/// it is unavailable, instead of the control vanishing under their focus.
|
||||||
|
final bool actionEnabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
@ -81,7 +87,7 @@ class AppBanner extends StatelessWidget {
|
||||||
Align(
|
Align(
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: onAction,
|
onPressed: actionEnabled ? onAction : null,
|
||||||
style: TextButton.styleFrom(foregroundColor: foreground),
|
style: TextButton.styleFrom(foregroundColor: foreground),
|
||||||
child: Text(actionLabel!),
|
child: Text(actionLabel!),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:ui' show Tristate;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:shuffle/core/widgets/app_banner.dart';
|
import 'package:shuffle/core/widgets/app_banner.dart';
|
||||||
|
|
@ -46,6 +48,39 @@ void main() {
|
||||||
handle.dispose();
|
handle.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('a disabled action stays visible and announced as disabled', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final handle = tester.ensureSemantics();
|
||||||
|
var calls = 0;
|
||||||
|
await pumpWidgetUnderTest(
|
||||||
|
tester,
|
||||||
|
AppBanner(
|
||||||
|
type: AppBannerType.info,
|
||||||
|
typeLabel: 'Note',
|
||||||
|
message: 'Confirm first',
|
||||||
|
actionLabel: 'Resend',
|
||||||
|
onAction: () => calls++,
|
||||||
|
actionEnabled: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final action = find.widgetWithText(TextButton, 'Resend');
|
||||||
|
expect(action, findsOneWidget);
|
||||||
|
expect(tester.widget<TextButton>(action).onPressed, isNull);
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.getSemantics(action)
|
||||||
|
.getSemanticsData()
|
||||||
|
.flagsCollection
|
||||||
|
.isEnabled,
|
||||||
|
Tristate.isFalse,
|
||||||
|
);
|
||||||
|
await tester.tap(action, warnIfMissed: false);
|
||||||
|
expect(calls, 0);
|
||||||
|
handle.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('action calls back and is at least 48 dp', (tester) async {
|
testWidgets('action calls back and is at least 48 dp', (tester) async {
|
||||||
var calls = 0;
|
var calls = 0;
|
||||||
await pumpWidgetUnderTest(
|
await pumpWidgetUnderTest(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue