// 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 (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.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, { 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), ); } }