Skip to content

Commit d60e38c

Browse files
Add support for universal banner messages (#8995)
1 parent 0b9cdee commit d60e38c

2 files changed

Lines changed: 72 additions & 20 deletions

File tree

packages/devtools_app/lib/src/shared/managers/banner_messages.dart

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ const _runInProfileModeDocsUrl = 'https://flutter.dev/to/use-profile-mode';
2323
const _cpuSamplingRateDocsUrl =
2424
'https://docs.flutter.dev/tools/devtools/cpu-profiler#cpu-sampling-rate';
2525

26+
/// Screen id to use for banner messages that are intended to be universal for
27+
/// every DevTools screen.
28+
///
29+
/// Messages with this screen id will be added to the list of messages for
30+
/// every screen from the [BannerMessages] widget.
31+
const universalBannerMessageId = 'universal';
32+
2633
class BannerMessagesController {
2734
final _messages = <String, ListValueNotifier<BannerMessage>>{};
2835
final _dismissedMessageKeys = <Key?>{};
@@ -77,13 +84,13 @@ class BannerMessagesController {
7784
});
7885
}
7986

80-
void removeMessageByKey(Key key, String screenId) {
87+
void removeMessageByKey(Key key, String screenId, {bool dismiss = false}) {
8188
final currentMessages = _messagesForScreen(screenId);
8289
final messageWithKey = currentMessages.value.firstWhereOrNull(
8390
(m) => m.key == key,
8491
);
8592
if (messageWithKey != null) {
86-
removeMessage(messageWithKey);
93+
removeMessage(messageWithKey, dismiss: dismiss);
8794
}
8895
}
8996

@@ -119,13 +126,18 @@ class BannerMessages extends StatelessWidget {
119126
// TODO(kenz): use an AnimatedList for message changes.
120127
@override
121128
Widget build(BuildContext context) {
129+
final universalMessages = bannerMessages.messagesForScreen(
130+
universalBannerMessageId,
131+
);
122132
final messagesForScreen = bannerMessages.messagesForScreen(screen.screenId);
123133
return Column(
124134
children: [
125-
ValueListenableBuilder<List<BannerMessage>>(
126-
valueListenable: messagesForScreen,
127-
builder: (context, messages, _) {
128-
return Column(children: messages);
135+
MultiValueListenableBuilder(
136+
listenables: [universalMessages, messagesForScreen],
137+
builder: (context, values, _) {
138+
final universalMessages = values[0] as List<BannerMessage>;
139+
final messages = values[1] as List<BannerMessage>;
140+
return Column(children: [...universalMessages, ...messages]);
129141
},
130142
),
131143
Expanded(child: screen.build(context)),

packages/devtools_app/test/shared/managers/banner_messages_test.dart

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
44

5+
import 'package:devtools_app/devtools_app.dart';
56
import 'package:devtools_app/src/framework/scaffold/scaffold.dart';
6-
import 'package:devtools_app/src/service/service_manager.dart';
7-
import 'package:devtools_app/src/shared/globals.dart';
8-
import 'package:devtools_app/src/shared/managers/banner_messages.dart';
9-
import 'package:devtools_app/src/shared/managers/notifications.dart';
107
import 'package:devtools_app_shared/ui.dart';
118
import 'package:devtools_app_shared/utils.dart';
129
import 'package:devtools_test/devtools_test.dart';
@@ -34,20 +31,12 @@ void main() {
3431
await tester.pumpAndSettle();
3532
}
3633

37-
Widget buildBannerMessages() {
34+
Widget buildBannerMessages({Screen? screen}) {
3835
return wrap(
3936
Directionality(
4037
textDirection: TextDirection.ltr,
4138
child: BannerMessages(
42-
screen: SimpleScreen(
43-
Column(
44-
children: <Widget>[
45-
// This is button is present so that we can tap it and
46-
// simulate a frame being drawn.
47-
ElevatedButton(onPressed: () => {}, child: const SizedBox()),
48-
],
49-
),
50-
),
39+
screen: screen ?? SimpleScreen(const _TestScreenBody()),
5140
),
5241
),
5342
);
@@ -66,6 +55,18 @@ void main() {
6655
expect(find.byKey(k2), findsOneWidget);
6756
});
6857

58+
testWidgets('displays universal banner messages for every screen', (
59+
WidgetTester tester,
60+
) async {
61+
await tester.pumpWidget(buildBannerMessages());
62+
bannerMessages.addMessage(universalMessage);
63+
await pumpTestFrame(tester);
64+
expect(find.byKey(kUniversal), findsOneWidget);
65+
66+
await tester.pumpWidget(buildBannerMessages(screen: TestScreen()));
67+
expect(find.byKey(kUniversal), findsOneWidget);
68+
});
69+
6970
testWidgets('does not add duplicate messages', (WidgetTester tester) async {
7071
await tester.pumpWidget(buildBannerMessages());
7172
expect(find.byKey(k1), findsNothing);
@@ -164,17 +165,56 @@ void main() {
164165

165166
final testMessage1ScreenId = SimpleScreen.id;
166167
final testMessage2ScreenId = SimpleScreen.id;
168+
167169
const k1 = Key('test message 1');
168170
const k2 = Key('test message 2');
171+
const kUniversal = Key('universal message');
172+
169173
final testMessage1 = BannerMessage(
170174
key: k1,
171175
buildTextSpans: (_) => const [TextSpan(text: 'Test Message 1')],
172176
screenId: testMessage1ScreenId,
173177
messageType: BannerMessageType.warning,
174178
);
179+
175180
final testMessage2 = BannerMessage(
176181
key: k2,
177182
buildTextSpans: (_) => const [TextSpan(text: 'Test Message 2')],
178183
screenId: testMessage2ScreenId,
179184
messageType: BannerMessageType.warning,
180185
);
186+
187+
final universalMessage = BannerMessage(
188+
key: kUniversal,
189+
buildTextSpans: (_) => const [TextSpan(text: 'Universal Message')],
190+
screenId: universalBannerMessageId,
191+
messageType: BannerMessageType.warning,
192+
);
193+
194+
class TestScreen extends Screen {
195+
TestScreen() : super(id, showFloatingDebuggerControls: false);
196+
197+
// This is arbitrary for the test. It just needs to be something different
198+
// than [ScreenMetaData.simple.id].
199+
static final id = ScreenMetaData.logging.id;
200+
201+
@override
202+
Widget buildScreenBody(BuildContext context) {
203+
return const _TestScreenBody();
204+
}
205+
}
206+
207+
class _TestScreenBody extends StatelessWidget {
208+
const _TestScreenBody();
209+
210+
@override
211+
Widget build(BuildContext context) {
212+
return Column(
213+
children: <Widget>[
214+
// This button is present so that we can tap it and
215+
// simulate a frame being drawn.
216+
ElevatedButton(onPressed: () => {}, child: const SizedBox()),
217+
],
218+
);
219+
}
220+
}

0 commit comments

Comments
 (0)