demo.dart 7.14 KB
Newer Older
1 2 3 4
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:async';

7
import 'package:flutter/cupertino.dart';
8
import 'package:flutter/material.dart';
9
import 'package:url_launcher/url_launcher.dart';
10

11
import 'demos.dart';
12
import 'example_code_parser.dart';
13
import 'syntax_highlighter.dart';
14

15 16
class ComponentDemoTabData {
  ComponentDemoTabData({
17
    this.demoWidget,
18
    this.exampleCodeTag,
19
    this.description,
20 21
    this.tabName,
    this.documentationUrl,
22 23
  });

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

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

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

class TabbedComponentDemoScaffold extends StatelessWidget {
45
  const TabbedComponentDemoScaffold({
46
    this.title,
47 48
    this.demos,
    this.actions,
49 50 51 52
  });

  final List<ComponentDemoTabData> demos;
  final String title;
53
  final List<Widget> actions;
54

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

64
  Future<void> _showApiDocumentation(BuildContext context) async {
65
    final String url = demos[DefaultTabController.of(context).index].documentationUrl;
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
    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),
              ),
            ],
          );
        },
      );
86 87 88
    }
  }

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

146
class FullScreenCodeDialog extends StatefulWidget {
147
  const FullScreenCodeDialog({ this.exampleCodeTag });
148 149 150 151

  final String exampleCodeTag;

  @override
152
  FullScreenCodeDialogState createState() => FullScreenCodeDialogState();
153 154 155
}

class FullScreenCodeDialogState extends State<FullScreenCodeDialog> {
156

157 158 159
  String _exampleCode;

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

  @override
  Widget build(BuildContext context) {
173 174 175 176
    final SyntaxHighlighterStyle style = Theme.of(context).brightness == Brightness.dark
      ? SyntaxHighlighterStyle.darkThemeStyle()
      : SyntaxHighlighterStyle.lightThemeStyle();

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

198 199 200
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
201 202 203 204
          icon: const Icon(
            Icons.clear,
            semanticLabel: 'Close',
          ),
205
          onPressed: () { Navigator.pop(context); },
206
        ),
207
        title: const Text('Example code'),
208
      ),
209
      body: body,
210 211 212
    );
  }
}
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228

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),
229
      tooltip: 'API documentation',
230
      onPressed: () => launch(documentationUrl, forceWebView: true),
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
    );
  }
}

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,
250 251 252 253
      child: Semantics(
        label: 'API documentation',
        child: const Icon(CupertinoIcons.book),
      ),
254
      onPressed: () => launch(documentationUrl, forceWebView: true),
255 256 257
    );
  }
}