diff --git a/packages/devtools_app/lib/src/screens/deep_link_validation/project_root_selection/select_project_view.dart b/packages/devtools_app/lib/src/screens/deep_link_validation/project_root_selection/select_project_view.dart index abd3dd989dc..d022c479d44 100644 --- a/packages/devtools_app/lib/src/screens/deep_link_validation/project_root_selection/select_project_view.dart +++ b/packages/devtools_app/lib/src/screens/deep_link_validation/project_root_selection/select_project_view.dart @@ -13,12 +13,15 @@ import '../../../shared/analytics/constants.dart' as gac; import '../../../shared/globals.dart'; import '../../../shared/primitives/utils.dart'; import '../../../shared/server/server.dart' as server; +import '../../../shared/ui/common_widgets.dart'; import '../deep_links_controller.dart'; import '../deep_links_model.dart'; import 'root_selector.dart'; const _kLinearProgressIndicatorWidth = 280.0; +enum DeepLinksTarget { android, ios } + /// A view for selecting a Flutter project. class SelectProjectView extends StatefulWidget { const SelectProjectView({super.key}); @@ -131,7 +134,7 @@ class _SelectProjectViewState extends State { gac.deeplink, gac.AnalyzeFlutterProject.flutterInvalidProjectSelected.name, ); - await showNonFlutterProjectDialog(); + await _showFlutterProjectMissingBuildOptionsDialog(directory); return; } controller.selectedProject.value = FlutterProject( @@ -144,6 +147,119 @@ class _SelectProjectViewState extends State { }); } + Future _showFlutterProjectMissingBuildOptionsDialog( + String appPath, + ) async { + await showDialog( + context: context, + builder: (_) { + final theme = Theme.of(context); + return DevToolsDialog( + title: const Text('No iOS or Android build options found.'), + content: SizedBox( + width: defaultDialogWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'DevTools could not verify the build options for this project.', + ), + const SizedBox(height: largeSpacing), + ..._deepLinksInstructions( + theme: theme, + appPath: appPath, + target: DeepLinksTarget.android, + ), + const SizedBox(height: largeSpacing), + ..._deepLinksInstructions( + theme: theme, + appPath: appPath, + target: DeepLinksTarget.ios, + ), + ], + ), + ), + actions: const [DialogCloseButton()], + ); + }, + ); + setState(() { + _retrievingFlutterProject = false; + }); + } + + List _deepLinksInstructions({ + required ThemeData theme, + required String appPath, + required DeepLinksTarget target, + }) { + final commandStyle = theme.subtleTextStyle.copyWith( + fontFamily: 'RobotoMono', + ); + const commandPadding = EdgeInsets.symmetric( + horizontal: denseSpacing, + vertical: densePadding, + ); + final title = switch (target) { + DeepLinksTarget.android => 'Android', + DeepLinksTarget.ios => 'iOS', + }; + final directory = switch (target) { + DeepLinksTarget.android => '/android', + DeepLinksTarget.ios => '/ios', + }; + final documentationUrl = switch (target) { + DeepLinksTarget.android => 'https://docs.flutter.dev/deployment/android', + DeepLinksTarget.ios => 'https://docs.flutter.dev/deployment/ios', + }; + final command = switch (target) { + DeepLinksTarget.android => + 'flutter analyze --android --list-build-variants', + DeepLinksTarget.ios => 'flutter analyze --ios --list-build-options', + }; + + return [ + Text('For $title', style: theme.textTheme.titleMedium), + const SizedBox(height: intermediateSpacing), + RichText( + text: TextSpan( + style: theme.regularTextStyle, + children: [ + TextSpan( + text: + 'These are configured in the $directory directory. Please refer to the ', + ), + GaLinkTextSpan( + link: GaLink( + display: 'Flutter documentation', + url: documentationUrl, + ), + context: context, + ), + const TextSpan(text: ' for more information.'), + ], + ), + ), + const SizedBox(height: intermediateSpacing), + const Text( + 'To confirm your setup, run the following command in your terminal:', + ), + const SizedBox(height: denseSpacing), + Card( + child: Padding( + padding: commandPadding, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible(child: Text('$command $appPath', style: commandStyle)), + CopyToClipboardControl(dataProvider: () => '$command $appPath'), + ], + ), + ), + ), + ]; + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -156,7 +272,7 @@ class _SelectProjectViewState extends State { Padding( padding: const EdgeInsets.all(extraLargeSpacing), child: Text( - 'Select a local flutter project to check the status of all deep links.', + 'Select a local Flutter project to check the status of all deep links.', textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleMedium, ), diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 1a050036a9f..9831e18f96e 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -51,7 +51,9 @@ TODO: Remove this section if there are not any general updates. ## Deep links tool updates -TODO: Remove this section if there are not any general updates. +- Added more informative dialog if Deep Links tool is unable to find build + options for the iOS or Android app. - + [#9571](https://github.com/flutter/devtools/pull/9571) ## VS Code Sidebar updates diff --git a/packages/devtools_app/test/screens/deep_link_validation/select_project_view_test.dart b/packages/devtools_app/test/screens/deep_link_validation/select_project_view_test.dart index a180bdbbef3..7602c18c4fb 100644 --- a/packages/devtools_app/test/screens/deep_link_validation/select_project_view_test.dart +++ b/packages/devtools_app/test/screens/deep_link_validation/select_project_view_test.dart @@ -63,7 +63,7 @@ void main() { ) async { await pumpSelectProjectView(tester); expect( - find.textContaining('Select a local flutter project to check'), + find.textContaining('Select a local Flutter project to check'), findsOneWidget, ); expect(find.byType(ProjectRootsDropdown), findsOneWidget); @@ -95,7 +95,7 @@ void main() { (WidgetTester tester) async { await pumpSelectProjectView(tester); expect( - find.textContaining('Select a local flutter project to check'), + find.textContaining('Select a local Flutter project to check'), findsOneWidget, ); expect(find.byType(ProjectRootsDropdown), findsOneWidget);