diff --git a/pkgs/sketch_pad/lib/flutter_samples.dart b/pkgs/sketch_pad/lib/flutter_samples.dart new file mode 100644 index 000000000..42209c7d3 --- /dev/null +++ b/pkgs/sketch_pad/lib/flutter_samples.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:http/http.dart' as http; + +class FlutterSampleLoader { + final http.Client client = http.Client(); + + Future loadFlutterSample({ + required String sampleId, + String? channel, + }) async { + // There are only two hosted versions of the docs: master/main and stable. + final sampleUrl = switch (channel) { + 'master' => 'https://main-api.flutter.dev/snippets/$sampleId.dart', + 'main' => 'https://main-api.flutter.dev/snippets/$sampleId.dart', + _ => 'https://api.flutter.dev/snippets/$sampleId.dart', + }; + + final response = await client.get(Uri.parse(sampleUrl)); + + if (response.statusCode != 200) { + throw Exception('Unable to load sample ' + '(${response.statusCode} ${response.reasonPhrase}})'); + } + + return response.body; + } + + void dispose() { + client.close(); + } +} diff --git a/pkgs/sketch_pad/lib/main.dart b/pkgs/sketch_pad/lib/main.dart index bb34900ad..00542b592 100644 --- a/pkgs/sketch_pad/lib/main.dart +++ b/pkgs/sketch_pad/lib/main.dart @@ -28,8 +28,6 @@ import 'utils.dart'; import 'versions.dart'; import 'widgets.dart'; -// TODO: explore using the monaco editor - // TODO: show documentation on hover // TODO: implement find / find next @@ -112,8 +110,9 @@ class _DartPadAppState extends State { } Widget _homePageBuilder(BuildContext context, GoRouterState state) { - final idParam = state.uri.queryParameters['id']; - final sampleParam = state.uri.queryParameters['sample']; + final gistId = state.uri.queryParameters['id']; + final builtinSampleId = state.uri.queryParameters['sample']; + final flutterSampleId = state.uri.queryParameters['sample_id']; final channelParam = state.uri.queryParameters['channel']; final embedMode = state.uri.queryParameters['embed'] == 'true'; final runOnLoad = state.uri.queryParameters['run'] == 'true'; @@ -123,8 +122,9 @@ class _DartPadAppState extends State { initialChannel: channelParam, embedMode: embedMode, runOnLoad: runOnLoad, - sampleId: sampleParam, - gistId: idParam, + gistId: gistId, + builtinSampleId: builtinSampleId, + flutterSampleId: flutterSampleId, handleBrightnessChanged: handleBrightnessChanged, ); } @@ -171,11 +171,12 @@ class _DartPadAppState extends State { class DartPadMainPage extends StatefulWidget { final String title; final String? initialChannel; - final String? sampleId; - final String? gistId; final bool embedMode; final bool runOnLoad; final void Function(BuildContext, bool) handleBrightnessChanged; + final String? gistId; + final String? builtinSampleId; + final String? flutterSampleId; DartPadMainPage({ required this.title, @@ -183,9 +184,14 @@ class DartPadMainPage extends StatefulWidget { required this.embedMode, required this.runOnLoad, required this.handleBrightnessChanged, - this.sampleId, this.gistId, - }) : super(key: ValueKey('sample:$sampleId gist:$gistId')); + this.builtinSampleId, + this.flutterSampleId, + }) : super( + key: ValueKey( + 'sample:$builtinSampleId gist:$gistId flutter:$flutterSampleId', + ), + ); @override State createState() => _DartPadMainPageState(); @@ -222,8 +228,10 @@ class _DartPadMainPageState extends State { appServices .performInitialLoad( - sampleId: widget.sampleId, gistId: widget.gistId, + sampleId: widget.builtinSampleId, + flutterSampleId: widget.flutterSampleId, + channel: widget.initialChannel, fallbackSnippet: Samples.getDefault(type: 'dart')) .then((value) { if (widget.runOnLoad) { diff --git a/pkgs/sketch_pad/lib/model.dart b/pkgs/sketch_pad/lib/model.dart index 8d529a046..3dd6c5b9f 100644 --- a/pkgs/sketch_pad/lib/model.dart +++ b/pkgs/sketch_pad/lib/model.dart @@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; +import 'flutter_samples.dart'; import 'gists.dart'; import 'samples.g.dart'; import 'utils.dart'; @@ -189,8 +190,10 @@ class AppServices { } Future performInitialLoad({ - String? sampleId, String? gistId, + String? sampleId, + String? flutterSampleId, + String? channel, required String fallbackSnippet, }) async { // Delay a bit for codemirror to initialize. @@ -204,42 +207,75 @@ class AppServices { return; } - if (gistId == null) { - appModel.sourceCodeController.text = fallbackSnippet; - appModel.appReady.value = true; - return; - } + if (flutterSampleId != null) { + final loader = FlutterSampleLoader(); + final progress = + appModel.editorStatus.showMessage(initialText: 'Loading…'); + try { + final sample = await loader.loadFlutterSample( + sampleId: flutterSampleId, + channel: channel, + ); + progress.close(); - final gistLoader = GistLoader(); - final progress = appModel.editorStatus.showMessage(initialText: 'Loading…'); - try { - final gist = await gistLoader.load(gistId); - progress.close(); + appModel.title.value = flutterSampleId; + appModel.sourceCodeController.text = sample; + + appModel.appReady.value = true; + } catch (e) { + appModel.editorStatus.showToast('Error loading sample'); + progress.close(); - final title = gist.description ?? ''; - appModel.title.value = - title.length > 40 ? '${title.substring(0, 40)}…' : title; + appModel.appendLineToConsole('Error loading sample: $e'); - final source = gist.mainDartSource; - if (source == null) { - appModel.editorStatus.showToast('main.dart not found'); appModel.sourceCodeController.text = fallbackSnippet; - } else { - appModel.sourceCodeController.text = source; + appModel.appReady.value = true; + } finally { + loader.dispose(); } - appModel.appReady.value = true; - } catch (e) { - appModel.editorStatus.showToast('Error loading gist'); - progress.close(); + return; + } - appModel.appendLineToConsole('Error loading gist: $e'); + if (gistId != null) { + final gistLoader = GistLoader(); + final progress = + appModel.editorStatus.showMessage(initialText: 'Loading…'); + try { + final gist = await gistLoader.load(gistId); + progress.close(); + + final title = gist.description ?? ''; + appModel.title.value = + title.length > 40 ? '${title.substring(0, 40)}…' : title; + + final source = gist.mainDartSource; + if (source == null) { + appModel.editorStatus.showToast('main.dart not found'); + appModel.sourceCodeController.text = fallbackSnippet; + } else { + appModel.sourceCodeController.text = source; + } + + appModel.appReady.value = true; + } catch (e) { + appModel.editorStatus.showToast('Error loading gist'); + progress.close(); + + appModel.appendLineToConsole('Error loading gist: $e'); - appModel.sourceCodeController.text = fallbackSnippet; - appModel.appReady.value = true; - } finally { - gistLoader.dispose(); + appModel.sourceCodeController.text = fallbackSnippet; + appModel.appReady.value = true; + } finally { + gistLoader.dispose(); + } + + return; } + + // Neither gistId nor flutterSampleId were passed in. + appModel.sourceCodeController.text = fallbackSnippet; + appModel.appReady.value = true; } Future format(SourceRequest request) async {