mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Bumps the Dart version to 3.8 across the repo (excluding engine/src/flutter/third_party) and applies formatting updates from Dart 3.8. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
259 lines
7.5 KiB
Dart
259 lines
7.5 KiB
Dart
// Copyright 2014 The Flutter 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 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
|
|
import 'demos.dart';
|
|
import 'example_code_parser.dart';
|
|
import 'syntax_highlighter.dart';
|
|
|
|
@immutable
|
|
class ComponentDemoTabData {
|
|
const ComponentDemoTabData({
|
|
this.demoWidget,
|
|
this.exampleCodeTag,
|
|
this.description,
|
|
this.tabName,
|
|
this.documentationUrl,
|
|
});
|
|
|
|
final Widget? demoWidget;
|
|
final String? exampleCodeTag;
|
|
final String? description;
|
|
final String? tabName;
|
|
final String? documentationUrl;
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (other.runtimeType != runtimeType) {
|
|
return false;
|
|
}
|
|
return other is ComponentDemoTabData &&
|
|
other.tabName == tabName &&
|
|
other.description == description &&
|
|
other.documentationUrl == documentationUrl;
|
|
}
|
|
|
|
@override
|
|
int get hashCode => Object.hash(tabName, description, documentationUrl);
|
|
}
|
|
|
|
class TabbedComponentDemoScaffold extends StatefulWidget {
|
|
const TabbedComponentDemoScaffold({
|
|
super.key,
|
|
this.title,
|
|
this.demos,
|
|
this.actions,
|
|
this.isScrollable = true,
|
|
this.showExampleCodeAction = true,
|
|
});
|
|
|
|
final List<ComponentDemoTabData>? demos;
|
|
final String? title;
|
|
final List<Widget>? actions;
|
|
final bool isScrollable;
|
|
final bool showExampleCodeAction;
|
|
|
|
@override
|
|
State<TabbedComponentDemoScaffold> createState() => _TabbedComponentDemoScaffoldState();
|
|
}
|
|
|
|
class _TabbedComponentDemoScaffoldState extends State<TabbedComponentDemoScaffold> {
|
|
void _showExampleCode(BuildContext context) {
|
|
final String? tag = widget.demos![DefaultTabController.of(context).index].exampleCodeTag;
|
|
if (tag != null) {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute<FullScreenCodeDialog>(
|
|
builder: (BuildContext context) => FullScreenCodeDialog(exampleCodeTag: tag),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _showApiDocumentation(BuildContext context) async {
|
|
final String? url = widget.demos![DefaultTabController.of(context).index].documentationUrl;
|
|
if (url == null) {
|
|
return;
|
|
}
|
|
|
|
final Uri uri = Uri.parse(url);
|
|
if (await canLaunchUrl(uri)) {
|
|
await launchUrl(uri);
|
|
} else if (context.mounted) {
|
|
showDialog<void>(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return SimpleDialog(
|
|
title: const Text("Couldn't display URL:"),
|
|
children: <Widget>[
|
|
Padding(padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Text(url)),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return DefaultTabController(
|
|
length: widget.demos!.length,
|
|
child: Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(widget.title!),
|
|
actions: <Widget>[
|
|
...?widget.actions,
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
return IconButton(
|
|
icon: const Icon(Icons.library_books, semanticLabel: 'Show documentation'),
|
|
onPressed: () => _showApiDocumentation(context),
|
|
);
|
|
},
|
|
),
|
|
if (widget.showExampleCodeAction)
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
return IconButton(
|
|
icon: const Icon(Icons.code),
|
|
tooltip: 'Show example code',
|
|
onPressed: () => _showExampleCode(context),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
bottom: TabBar(
|
|
isScrollable: widget.isScrollable,
|
|
tabs: widget.demos!
|
|
.map<Widget>((ComponentDemoTabData data) => Tab(text: data.tabName))
|
|
.toList(),
|
|
),
|
|
),
|
|
body: TabBarView(
|
|
children: widget.demos!.map<Widget>((ComponentDemoTabData demo) {
|
|
return SafeArea(
|
|
top: false,
|
|
bottom: false,
|
|
child: Column(
|
|
children: <Widget>[
|
|
Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Text(demo.description!, style: Theme.of(context).textTheme.titleMedium),
|
|
),
|
|
Expanded(child: demo.demoWidget!),
|
|
],
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class FullScreenCodeDialog extends StatefulWidget {
|
|
const FullScreenCodeDialog({super.key, this.exampleCodeTag});
|
|
|
|
final String? exampleCodeTag;
|
|
|
|
@override
|
|
FullScreenCodeDialogState createState() => FullScreenCodeDialogState();
|
|
}
|
|
|
|
class FullScreenCodeDialogState extends State<FullScreenCodeDialog> {
|
|
String? _exampleCode;
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
getExampleCode(widget.exampleCodeTag, DefaultAssetBundle.of(context)).then((String? code) {
|
|
if (mounted) {
|
|
setState(() {
|
|
_exampleCode = code ?? 'Example code not found';
|
|
});
|
|
}
|
|
});
|
|
super.didChangeDependencies();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final SyntaxHighlighterStyle style = Theme.brightnessOf(context) == Brightness.dark
|
|
? SyntaxHighlighterStyle.darkThemeStyle
|
|
: SyntaxHighlighterStyle.lightThemeStyle;
|
|
|
|
Widget body;
|
|
if (_exampleCode == null) {
|
|
body = const Center(child: CircularProgressIndicator());
|
|
} else {
|
|
body = SingleChildScrollView(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: RichText(
|
|
text: TextSpan(
|
|
style: const TextStyle(fontFamily: 'monospace', fontSize: 10.0),
|
|
children: <TextSpan>[DartSyntaxHighlighter(style).format(_exampleCode)],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.clear, semanticLabel: 'Close'),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
},
|
|
),
|
|
title: const Text('Example code'),
|
|
),
|
|
body: body,
|
|
);
|
|
}
|
|
}
|
|
|
|
class MaterialDemoDocumentationButton extends StatelessWidget {
|
|
MaterialDemoDocumentationButton(String routeName, {super.key})
|
|
: documentationUrl = kDemoDocumentationUrl[routeName],
|
|
assert(
|
|
kDemoDocumentationUrl[routeName] != null,
|
|
'A documentation URL was not specified for demo route $routeName in kAllGalleryDemos',
|
|
);
|
|
|
|
final String? documentationUrl;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return IconButton(
|
|
icon: const Icon(Icons.library_books),
|
|
tooltip: 'API documentation',
|
|
onPressed: () => launchUrl(Uri.parse(documentationUrl!), mode: LaunchMode.inAppWebView),
|
|
);
|
|
}
|
|
}
|
|
|
|
class CupertinoDemoDocumentationButton extends StatelessWidget {
|
|
CupertinoDemoDocumentationButton(String routeName, {super.key})
|
|
: documentationUrl = kDemoDocumentationUrl[routeName],
|
|
assert(
|
|
kDemoDocumentationUrl[routeName] != null,
|
|
'A documentation URL was not specified for demo route $routeName in kAllGalleryDemos',
|
|
);
|
|
|
|
final String? documentationUrl;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return CupertinoButton(
|
|
padding: EdgeInsets.zero,
|
|
child: Semantics(label: 'API documentation', child: const Icon(CupertinoIcons.book)),
|
|
onPressed: () => launchUrl(Uri.parse(documentationUrl!), mode: LaunchMode.inAppWebView),
|
|
);
|
|
}
|
|
}
|