Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ const _runInProfileModeDocsUrl = 'https://flutter.dev/to/use-profile-mode';
const _cpuSamplingRateDocsUrl =
'https://docs.flutter.dev/tools/devtools/cpu-profiler#cpu-sampling-rate';

/// Screen id to use for banner messages that are intended to be universal for
/// every DevTools screen.
///
/// Messages with this screen id will be added to the list of messages for
/// every screen from the [BannerMessages] widget.
const universalBannerMessageId = 'universal';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the banner ID the same as the banner key? (Can there be more than one universal banner? Does dismissing one of them dismiss all of them?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. No this is the ID to use for the screen, not the ID for the banner message itself. There can be more than one banner message with this screen id, much like there can be multiple banner messages for any other screen id. I will rename this to universalScreenId to make this less confusing. Banner messages are still identified by their key.


class BannerMessagesController {
final _messages = <String, ListValueNotifier<BannerMessage>>{};
final _dismissedMessageKeys = <Key?>{};
Expand Down Expand Up @@ -77,13 +84,13 @@ class BannerMessagesController {
});
}

void removeMessageByKey(Key key, String screenId) {
void removeMessageByKey(Key key, String screenId, {bool dismiss = false}) {
final currentMessages = _messagesForScreen(screenId);
final messageWithKey = currentMessages.value.firstWhereOrNull(
(m) => m.key == key,
);
if (messageWithKey != null) {
removeMessage(messageWithKey);
removeMessage(messageWithKey, dismiss: dismiss);
}
}

Expand Down Expand Up @@ -119,13 +126,18 @@ class BannerMessages extends StatelessWidget {
// TODO(kenz): use an AnimatedList for message changes.
@override
Widget build(BuildContext context) {
final universalMessages = bannerMessages.messagesForScreen(
universalBannerMessageId,
);
final messagesForScreen = bannerMessages.messagesForScreen(screen.screenId);
return Column(
children: [
ValueListenableBuilder<List<BannerMessage>>(
valueListenable: messagesForScreen,
builder: (context, messages, _) {
return Column(children: messages);
MultiValueListenableBuilder(
listenables: [universalMessages, messagesForScreen],
builder: (context, values, _) {
final universalMessages = values[0] as List<BannerMessage>;
final messages = values[1] as List<BannerMessage>;
return Column(children: [...universalMessages, ...messages]);
},
),
Expanded(child: screen.build(context)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.

import 'package:devtools_app/devtools_app.dart';
import 'package:devtools_app/src/framework/scaffold/scaffold.dart';
import 'package:devtools_app/src/service/service_manager.dart';
import 'package:devtools_app/src/shared/globals.dart';
import 'package:devtools_app/src/shared/managers/banner_messages.dart';
import 'package:devtools_app/src/shared/managers/notifications.dart';
import 'package:devtools_app_shared/ui.dart';
import 'package:devtools_app_shared/utils.dart';
import 'package:devtools_test/devtools_test.dart';
Expand Down Expand Up @@ -34,20 +31,12 @@ void main() {
await tester.pumpAndSettle();
}

Widget buildBannerMessages() {
Widget buildBannerMessages({Screen? screen}) {
return wrap(
Directionality(
textDirection: TextDirection.ltr,
child: BannerMessages(
screen: SimpleScreen(
Column(
children: <Widget>[
// This is button is present so that we can tap it and
// simulate a frame being drawn.
ElevatedButton(onPressed: () => {}, child: const SizedBox()),
],
),
),
screen: screen ?? SimpleScreen(const _TestScreenBody()),
),
),
);
Expand All @@ -66,6 +55,18 @@ void main() {
expect(find.byKey(k2), findsOneWidget);
});

testWidgets('displays universal banner messages for every screen', (
WidgetTester tester,
) async {
await tester.pumpWidget(buildBannerMessages());
bannerMessages.addMessage(universalMessage);
await pumpTestFrame(tester);
expect(find.byKey(kUniversal), findsOneWidget);

await tester.pumpWidget(buildBannerMessages(screen: TestScreen()));
expect(find.byKey(kUniversal), findsOneWidget);
});

testWidgets('does not add duplicate messages', (WidgetTester tester) async {
await tester.pumpWidget(buildBannerMessages());
expect(find.byKey(k1), findsNothing);
Expand Down Expand Up @@ -164,17 +165,56 @@ void main() {

final testMessage1ScreenId = SimpleScreen.id;
final testMessage2ScreenId = SimpleScreen.id;

const k1 = Key('test message 1');
const k2 = Key('test message 2');
const kUniversal = Key('universal message');

final testMessage1 = BannerMessage(
key: k1,
buildTextSpans: (_) => const [TextSpan(text: 'Test Message 1')],
screenId: testMessage1ScreenId,
messageType: BannerMessageType.warning,
);

final testMessage2 = BannerMessage(
key: k2,
buildTextSpans: (_) => const [TextSpan(text: 'Test Message 2')],
screenId: testMessage2ScreenId,
messageType: BannerMessageType.warning,
);

final universalMessage = BannerMessage(
key: kUniversal,
buildTextSpans: (_) => const [TextSpan(text: 'Universal Message')],
screenId: universalBannerMessageId,
messageType: BannerMessageType.warning,
);

class TestScreen extends Screen {
TestScreen() : super(id, showFloatingDebuggerControls: false);

// This is arbitrary for the test. It just needs to be something different
// than [ScreenMetaData.simple.id].
static final id = ScreenMetaData.logging.id;

@override
Widget buildScreenBody(BuildContext context) {
return const _TestScreenBody();
}
}

class _TestScreenBody extends StatelessWidget {
const _TestScreenBody();

@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
// This button is present so that we can tap it and
// simulate a frame being drawn.
ElevatedButton(onPressed: () => {}, child: const SizedBox()),
],
);
}
}