diff --git a/packages/devtools_app/lib/src/screens/logging/logging_controls.dart b/packages/devtools_app/lib/src/screens/logging/logging_controls.dart new file mode 100644 index 00000000000..e0f93efd62d --- /dev/null +++ b/packages/devtools_app/lib/src/screens/logging/logging_controls.dart @@ -0,0 +1,123 @@ +// Copyright 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:devtools_app_shared/ui.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../service/service_extension_widgets.dart'; +import '../../shared/analytics/constants.dart' as gac; +import '../../shared/common_widgets.dart'; +import '../../shared/primitives/utils.dart'; +import '../../shared/ui/filter.dart'; +import '../../shared/ui/search.dart'; +import 'logging_controller.dart'; +import 'shared/constants.dart'; + +class LoggingControls extends StatelessWidget { + const LoggingControls({super.key}); + + static const filterQueryInstructions = ''' +Type a filter query to show or hide specific logs. + +Any text that is not paired with an available filter key below will be queried against all categories (kind, message). + +Available filters: + 'kind', 'k' (e.g. 'k:flutter.frame', '-k:gc,stdout') + +Example queries: + 'my log message k:stdout,stdin' + 'flutter -k:gc' +'''; + + @override + Widget build(BuildContext context) { + final controller = Provider.of(context); + final hasData = controller.filteredData.value.isNotEmpty; + return Row( + children: [ + ClearButton( + onPressed: controller.clear, + gaScreen: gac.logging, + gaSelection: gac.clear, + minScreenWidthForTextBeforeScaling: loggingMinVerboseWidth, + ), + const Spacer(), + const SizedBox(width: denseSpacing), + // TODO(kenz): fix focus issue when state is refreshed + SearchField( + searchFieldWidth: isScreenWiderThan(context, loggingMinVerboseWidth) + ? wideSearchFieldWidth + : defaultSearchFieldWidth, + searchController: controller, + searchFieldEnabled: hasData, + ), + const SizedBox(width: denseSpacing), + DevToolsFilterButton( + onPressed: () { + unawaited( + showDialog( + context: context, + builder: (context) => FilterDialog( + controller: controller, + queryInstructions: filterQueryInstructions, + ), + ), + ); + }, + isFilterActive: controller.isFilterActive, + ), + const SizedBox(width: denseSpacing), + CopyToClipboardControl( + dataProvider: () => controller.filteredData.value + .map((e) => '${e.timestamp} [${e.kind}] ${e.prettyPrinted()}') + .joinWithTrailing('\n'), + tooltip: 'Copy filtered logs', + ), + const SizedBox(width: denseSpacing), + SettingsOutlinedButton( + gaScreen: gac.logging, + gaSelection: gac.loggingSettings, + tooltip: 'Logging Settings', + onPressed: () { + unawaited( + showDialog( + context: context, + builder: (context) => const LoggingSettingsDialog(), + ), + ); + }, + ), + ], + ); + } +} + +class LoggingSettingsDialog extends StatelessWidget { + const LoggingSettingsDialog({super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return DevToolsDialog( + title: const DialogTitleText('Logging Settings'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...dialogSubHeader( + theme, + 'General', + ), + const StructuredErrorsToggle(), + ], + ), + actions: const [ + DialogCloseButton(), + ], + ); + } +} diff --git a/packages/devtools_app/lib/src/screens/logging/logging_screen.dart b/packages/devtools_app/lib/src/screens/logging/logging_screen.dart index a5dbc304e8f..065f3e708b8 100644 --- a/packages/devtools_app/lib/src/screens/logging/logging_screen.dart +++ b/packages/devtools_app/lib/src/screens/logging/logging_screen.dart @@ -2,26 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - import 'package:devtools_app_shared/ui.dart'; import 'package:devtools_app_shared/utils.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../../service/service_extension_widgets.dart'; import '../../shared/analytics/analytics.dart' as ga; import '../../shared/analytics/constants.dart' as gac; -import '../../shared/common_widgets.dart'; -import '../../shared/primitives/utils.dart'; import '../../shared/screen.dart'; -import '../../shared/ui/filter.dart'; -import '../../shared/ui/search.dart'; import '../../shared/utils.dart'; import '_log_details.dart'; import '_logs_table.dart'; import 'logging_controller.dart'; -import 'shared/constants.dart'; +import 'logging_controls.dart'; /// Presents logs from the connected app. class LoggingScreen extends Screen { @@ -52,19 +45,6 @@ class LoggingScreen extends Screen { class LoggingScreenBody extends StatefulWidget { const LoggingScreenBody({super.key}); - static const filterQueryInstructions = ''' -Type a filter query to show or hide specific logs. - -Any text that is not paired with an available filter key below will be queried against all categories (kind, message). - -Available filters: - 'kind', 'k' (e.g. 'k:flutter.frame', '-k:gc,stdout') - -Example queries: - 'my log message k:stdout,stdin' - 'flutter -k:gc' -'''; - @override State createState() => _LoggingScreenState(); } @@ -92,7 +72,7 @@ class _LoggingScreenState extends State Widget build(BuildContext context) { return Column( children: [ - _buildLoggingControls(), + const LoggingControls(), const SizedBox(height: intermediateSpacing), Expanded( child: _buildLoggingBody(), @@ -101,57 +81,6 @@ class _LoggingScreenState extends State ); } - // TODO(kenz): replace with helper widget - Widget _buildLoggingControls() { - final hasData = controller.filteredData.value.isNotEmpty; - return Row( - children: [ - ClearButton( - onPressed: controller.clear, - gaScreen: gac.logging, - gaSelection: gac.clear, - minScreenWidthForTextBeforeScaling: loggingMinVerboseWidth, - ), - const Spacer(), - const SizedBox(width: denseSpacing), - // TODO(kenz): fix focus issue when state is refreshed - SearchField( - searchFieldWidth: isScreenWiderThan(context, loggingMinVerboseWidth) - ? wideSearchFieldWidth - : defaultSearchFieldWidth, - searchController: controller, - searchFieldEnabled: hasData, - ), - const SizedBox(width: denseSpacing), - DevToolsFilterButton( - onPressed: _showFilterDialog, - isFilterActive: controller.isFilterActive, - ), - const SizedBox(width: denseSpacing), - CopyToClipboardControl( - dataProvider: () => controller.filteredData.value - .map((e) => '${e.timestamp} [${e.kind}] ${e.prettyPrinted()}') - .joinWithTrailing('\n'), - tooltip: 'Copy filtered logs', - ), - const SizedBox(width: denseSpacing), - SettingsOutlinedButton( - gaScreen: gac.logging, - gaSelection: gac.loggingSettings, - tooltip: 'Logging Settings', - onPressed: () { - unawaited( - showDialog( - context: context, - builder: (context) => const LoggingSettingsDialog(), - ), - ); - }, - ), - ], - ); - } - // TODO(kenz): replace with helper widget. Widget _buildLoggingBody() { return SplitPane( @@ -179,42 +108,4 @@ class _LoggingScreenState extends State ], ); } - - void _showFilterDialog() { - unawaited( - showDialog( - context: context, - builder: (context) => FilterDialog( - controller: controller, - queryInstructions: LoggingScreenBody.filterQueryInstructions, - ), - ), - ); - } -} - -class LoggingSettingsDialog extends StatelessWidget { - const LoggingSettingsDialog({super.key}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return DevToolsDialog( - title: const DialogTitleText('Logging Settings'), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...dialogSubHeader( - theme, - 'General', - ), - const StructuredErrorsToggle(), - ], - ), - actions: const [ - DialogCloseButton(), - ], - ); - } }