demo.dart 7.32 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/cupertino.dart';
6
import 'package:flutter/material.dart';
7
import 'package:url_launcher/url_launcher.dart';
8

9
import 'demos.dart';
10
import 'example_code_parser.dart';
11
import 'syntax_highlighter.dart';
12

13
@immutable
14
class ComponentDemoTabData {
15
  const ComponentDemoTabData({
16
    this.demoWidget,
17
    this.exampleCodeTag,
18
    this.description,
19 20
    this.tabName,
    this.documentationUrl,
21 22
  });

23 24 25 26 27
  final Widget? demoWidget;
  final String? exampleCodeTag;
  final String? description;
  final String? tabName;
  final String? documentationUrl;
28 29 30 31 32

  @override
  bool operator==(Object other) {
    if (other.runtimeType != runtimeType)
      return false;
33 34 35 36
    return other is ComponentDemoTabData
        && other.tabName == tabName
        && other.description == description
        && other.documentationUrl == documentationUrl;
37 38 39
  }

  @override
40
  int get hashCode => hashValues(tabName, description, documentationUrl);
41 42 43
}

class TabbedComponentDemoScaffold extends StatelessWidget {
44
  const TabbedComponentDemoScaffold({
45
    Key? key,
46
    this.title,
47 48
    this.demos,
    this.actions,
49 50
    this.isScrollable = true,
    this.showExampleCodeAction = true,
51
  }) : super(key: key);
52

53 54 55
  final List<ComponentDemoTabData>? demos;
  final String? title;
  final List<Widget>? actions;
56 57
  final bool isScrollable;
  final bool showExampleCodeAction;
58

59
  void _showExampleCode(BuildContext context) {
60
    final String? tag = demos![DefaultTabController.of(context)!.index].exampleCodeTag;
61
    if (tag != null) {
62 63
      Navigator.push(context, MaterialPageRoute<FullScreenCodeDialog>(
        builder: (BuildContext context) => FullScreenCodeDialog(exampleCodeTag: tag)
64 65 66 67
      ));
    }
  }

68
  Future<void> _showApiDocumentation(BuildContext context) async {
69
    final String? url = demos![DefaultTabController.of(context)!.index].documentationUrl;
70 71 72 73 74 75 76 77 78 79
    if (url == null)
      return;

    if (await canLaunch(url)) {
      await launch(url);
    } else {
      showDialog<void>(
        context: context,
        builder: (BuildContext context) {
          return SimpleDialog(
80
            title: const Text("Couldn't display URL:"),
81 82 83 84 85 86 87 88 89
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 16.0),
                child: Text(url),
              ),
            ],
          );
        },
      );
90 91 92
    }
  }

93 94
  @override
  Widget build(BuildContext context) {
95
    return DefaultTabController(
96
      length: demos!.length,
97 98
      child: Scaffold(
        appBar: AppBar(
99
          title: Text(title!),
100 101
          actions: <Widget>[
            ...?actions,
102 103 104 105 106 107 108 109
            Builder(
              builder: (BuildContext context) {
                return IconButton(
                  icon: const Icon(Icons.library_books, semanticLabel: 'Show documentation'),
                  onPressed: () => _showApiDocumentation(context),
                );
              },
            ),
110 111 112 113 114 115 116 117 118 119 120
            if (showExampleCodeAction)
              Builder(
                builder: (BuildContext context) {
                  return IconButton(
                    icon: const Icon(Icons.code),
                    tooltip: 'Show example code',
                    onPressed: () => _showExampleCode(context),
                  );
                },
              ),
          ],
121
          bottom: TabBar(
122
            isScrollable: isScrollable,
123
            tabs: demos!.map<Widget>((ComponentDemoTabData data) => Tab(text: data.tabName)).toList(),
Hans Muller's avatar
Hans Muller committed
124
          ),
125
        ),
126
        body: TabBarView(
127
          children: demos!.map<Widget>((ComponentDemoTabData demo) {
128
            return SafeArea(
129 130
              top: false,
              bottom: false,
131
              child: Column(
132
                children: <Widget>[
133
                  Padding(
134
                    padding: const EdgeInsets.all(16.0),
135
                    child: Text(demo.description!,
136
                      style: Theme.of(context).textTheme.subtitle1,
137
                    ),
138
                  ),
139
                  Expanded(child: demo.demoWidget!),
140 141
                ],
              ),
142
            );
Hans Muller's avatar
Hans Muller committed
143 144 145
          }).toList(),
        ),
      ),
146 147 148 149
    );
  }
}

150
class FullScreenCodeDialog extends StatefulWidget {
151
  const FullScreenCodeDialog({ Key? key, this.exampleCodeTag }) : super(key: key);
152

153
  final String? exampleCodeTag;
154 155

  @override
156
  FullScreenCodeDialogState createState() => FullScreenCodeDialogState();
157 158 159
}

class FullScreenCodeDialogState extends State<FullScreenCodeDialog> {
160

161
  String? _exampleCode;
162 163

  @override
164
  void didChangeDependencies() {
165
    getExampleCode(widget.exampleCodeTag, DefaultAssetBundle.of(context)).then((String? code) {
166 167
      if (mounted) {
        setState(() {
168
          _exampleCode = code ?? 'Example code not found';
169 170
        });
      }
171
    });
172
    super.didChangeDependencies();
173
  }
174 175 176

  @override
  Widget build(BuildContext context) {
177 178 179 180
    final SyntaxHighlighterStyle style = Theme.of(context).brightness == Brightness.dark
      ? SyntaxHighlighterStyle.darkThemeStyle()
      : SyntaxHighlighterStyle.lightThemeStyle();

181 182
    Widget body;
    if (_exampleCode == null) {
183
      body = const Center(
184
        child: CircularProgressIndicator(),
185 186
      );
    } else {
187 188
      body = SingleChildScrollView(
        child: Padding(
189
          padding: const EdgeInsets.all(16.0),
190 191
          child: RichText(
            text: TextSpan(
192
              style: const TextStyle(fontFamily: 'monospace', fontSize: 10.0),
193
              children: <TextSpan>[
194 195 196 197 198
                DartSyntaxHighlighter(style).format(_exampleCode),
              ],
            ),
          ),
        ),
199 200 201
      );
    }

202 203 204
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
205 206 207 208
          icon: const Icon(
            Icons.clear,
            semanticLabel: 'Close',
          ),
209
          onPressed: () { Navigator.pop(context); },
210
        ),
211
        title: const Text('Example code'),
212
      ),
213
      body: body,
214 215 216
    );
  }
}
217 218

class MaterialDemoDocumentationButton extends StatelessWidget {
219
  MaterialDemoDocumentationButton(String routeName, { Key? key })
220 221 222 223 224 225 226
    : documentationUrl = kDemoDocumentationUrl[routeName],
      assert(
        kDemoDocumentationUrl[routeName] != null,
        'A documentation URL was not specified for demo route $routeName in kAllGalleryDemos',
      ),
      super(key: key);

227
  final String? documentationUrl;
228 229 230 231 232

  @override
  Widget build(BuildContext context) {
    return IconButton(
      icon: const Icon(Icons.library_books),
233
      tooltip: 'API documentation',
234
      onPressed: () => launch(documentationUrl!, forceWebView: true),
235 236 237 238 239
    );
  }
}

class CupertinoDemoDocumentationButton extends StatelessWidget {
240
  CupertinoDemoDocumentationButton(String routeName, { Key? key })
241 242 243 244 245 246 247
    : documentationUrl = kDemoDocumentationUrl[routeName],
      assert(
        kDemoDocumentationUrl[routeName] != null,
        'A documentation URL was not specified for demo route $routeName in kAllGalleryDemos',
      ),
      super(key: key);

248
  final String? documentationUrl;
249 250 251 252 253

  @override
  Widget build(BuildContext context) {
    return CupertinoButton(
      padding: EdgeInsets.zero,
254 255 256 257
      child: Semantics(
        label: 'API documentation',
        child: const Icon(CupertinoIcons.book),
      ),
258
      onPressed: () => launch(documentationUrl!, forceWebView: true),
259 260 261
    );
  }
}