| // 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 => hashValues(tabName, description, documentationUrl); |
| } |
| |
| class TabbedComponentDemoScaffold extends StatelessWidget { |
| const TabbedComponentDemoScaffold({ |
| Key? key, |
| this.title, |
| this.demos, |
| this.actions, |
| this.isScrollable = true, |
| this.showExampleCodeAction = true, |
| }) : super(key: key); |
| |
| final List<ComponentDemoTabData>? demos; |
| final String? title; |
| final List<Widget>? actions; |
| final bool isScrollable; |
| final bool showExampleCodeAction; |
| |
| void _showExampleCode(BuildContext context) { |
| final String? tag = 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 = demos![DefaultTabController.of(context)!.index].documentationUrl; |
| if (url == null) |
| return; |
| |
| if (await canLaunch(url)) { |
| await launch(url); |
| } else { |
| 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: demos!.length, |
| child: Scaffold( |
| appBar: AppBar( |
| title: Text(title!), |
| actions: <Widget>[ |
| ...?actions, |
| Builder( |
| builder: (BuildContext context) { |
| return IconButton( |
| icon: const Icon(Icons.library_books, semanticLabel: 'Show documentation'), |
| onPressed: () => _showApiDocumentation(context), |
| ); |
| }, |
| ), |
| if (showExampleCodeAction) |
| Builder( |
| builder: (BuildContext context) { |
| return IconButton( |
| icon: const Icon(Icons.code), |
| tooltip: 'Show example code', |
| onPressed: () => _showExampleCode(context), |
| ); |
| }, |
| ), |
| ], |
| bottom: TabBar( |
| isScrollable: isScrollable, |
| tabs: demos!.map<Widget>((ComponentDemoTabData data) => Tab(text: data.tabName)).toList(), |
| ), |
| ), |
| body: TabBarView( |
| children: 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.subtitle1, |
| ), |
| ), |
| Expanded(child: demo.demoWidget!), |
| ], |
| ), |
| ); |
| }).toList(), |
| ), |
| ), |
| ); |
| } |
| } |
| |
| class FullScreenCodeDialog extends StatefulWidget { |
| const FullScreenCodeDialog({ Key? key, this.exampleCodeTag }) : super(key: key); |
| |
| 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.of(context).brightness == 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, { Key? key }) |
| : documentationUrl = kDemoDocumentationUrl[routeName], |
| assert( |
| kDemoDocumentationUrl[routeName] != null, |
| 'A documentation URL was not specified for demo route $routeName in kAllGalleryDemos', |
| ), |
| super(key: key); |
| |
| final String? documentationUrl; |
| |
| @override |
| Widget build(BuildContext context) { |
| return IconButton( |
| icon: const Icon(Icons.library_books), |
| tooltip: 'API documentation', |
| onPressed: () => launch(documentationUrl!, forceWebView: true), |
| ); |
| } |
| } |
| |
| class CupertinoDemoDocumentationButton extends StatelessWidget { |
| CupertinoDemoDocumentationButton(String routeName, { Key? key }) |
| : documentationUrl = kDemoDocumentationUrl[routeName], |
| assert( |
| kDemoDocumentationUrl[routeName] != null, |
| 'A documentation URL was not specified for demo route $routeName in kAllGalleryDemos', |
| ), |
| super(key: key); |
| |
| final String? documentationUrl; |
| |
| @override |
| Widget build(BuildContext context) { |
| return CupertinoButton( |
| padding: EdgeInsets.zero, |
| child: Semantics( |
| label: 'API documentation', |
| child: const Icon(CupertinoIcons.book), |
| ), |
| onPressed: () => launch(documentationUrl!, forceWebView: true), |
| ); |
| } |
| } |