// 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. import 'dart:math' as math; import 'package:flutter/foundation.dart' show defaultTargetPlatform, required; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; class LinkTextSpan extends TextSpan { // Beware! // // This class is only safe because the TapGestureRecognizer is not // given a deadline and therefore never allocates any resources. // // In any other situation -- setting a deadline, using any of the less trivial // recognizers, etc -- you would have to manage the gesture recognizer's // lifetime and call dispose() when the TextSpan was no longer being rendered. // // Since TextSpan itself is @immutable, this means that you would have to // manage the recognizer from outside the TextSpan, e.g. in the State of a // stateful widget that then hands the recognizer to the TextSpan. LinkTextSpan({ TextStyle style, String url, String text }) : super( style: style, text: text ?? url, recognizer: new TapGestureRecognizer()..onTap = () { launch(url); } ); } class GalleryDrawerHeader extends StatefulWidget { const GalleryDrawerHeader({ Key key, this.light }) : super(key: key); final bool light; @override _GalleryDrawerHeaderState createState() => new _GalleryDrawerHeaderState(); } class _GalleryDrawerHeaderState extends State<GalleryDrawerHeader> { bool _logoHasName = true; bool _logoHorizontal = true; MaterialColor _logoColor = Colors.blue; @override Widget build(BuildContext context) { final double systemTopPadding = MediaQuery.of(context).padding.top; return new DrawerHeader( decoration: new FlutterLogoDecoration( margin: new EdgeInsets.fromLTRB(12.0, 12.0 + systemTopPadding, 12.0, 12.0), style: _logoHasName ? _logoHorizontal ? FlutterLogoStyle.horizontal : FlutterLogoStyle.stacked : FlutterLogoStyle.markOnly, lightColor: _logoColor.shade400, darkColor: _logoColor.shade900, textColor: widget.light ? const Color(0xFF616161) : const Color(0xFF9E9E9E), ), duration: const Duration(milliseconds: 750), child: new GestureDetector( onLongPress: () { setState(() { _logoHorizontal = !_logoHorizontal; if (!_logoHasName) _logoHasName = true; }); }, onTap: () { setState(() { _logoHasName = !_logoHasName; }); }, onDoubleTap: () { setState(() { final List<MaterialColor> options = <MaterialColor>[]; if (_logoColor != Colors.blue) options.addAll(<MaterialColor>[Colors.blue, Colors.blue, Colors.blue, Colors.blue, Colors.blue, Colors.blue, Colors.blue]); if (_logoColor != Colors.amber) options.addAll(<MaterialColor>[Colors.amber, Colors.amber, Colors.amber]); if (_logoColor != Colors.red) options.addAll(<MaterialColor>[Colors.red, Colors.red, Colors.red]); if (_logoColor != Colors.indigo) options.addAll(<MaterialColor>[Colors.indigo, Colors.indigo, Colors.indigo]); if (_logoColor != Colors.pink) options.addAll(<MaterialColor>[Colors.pink]); if (_logoColor != Colors.purple) options.addAll(<MaterialColor>[Colors.purple]); if (_logoColor != Colors.cyan) options.addAll(<MaterialColor>[Colors.cyan]); _logoColor = options[new math.Random().nextInt(options.length)]; }); } ) ); } } class GalleryDrawer extends StatelessWidget { const GalleryDrawer({ Key key, this.useLightTheme, @required this.onThemeChanged, this.timeDilation, @required this.onTimeDilationChanged, this.textScaleFactor, this.onTextScaleFactorChanged, this.showPerformanceOverlay, this.onShowPerformanceOverlayChanged, this.checkerboardRasterCacheImages, this.onCheckerboardRasterCacheImagesChanged, this.checkerboardOffscreenLayers, this.onCheckerboardOffscreenLayersChanged, this.onPlatformChanged, this.onSendFeedback, }) : assert(onThemeChanged != null), assert(onTimeDilationChanged != null), super(key: key); final bool useLightTheme; final ValueChanged<bool> onThemeChanged; final double timeDilation; final ValueChanged<double> onTimeDilationChanged; final double textScaleFactor; final ValueChanged<double> onTextScaleFactorChanged; final bool showPerformanceOverlay; final ValueChanged<bool> onShowPerformanceOverlayChanged; final bool checkerboardRasterCacheImages; final ValueChanged<bool> onCheckerboardRasterCacheImagesChanged; final bool checkerboardOffscreenLayers; final ValueChanged<bool> onCheckerboardOffscreenLayersChanged; final ValueChanged<TargetPlatform> onPlatformChanged; final VoidCallback onSendFeedback; @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); final TextStyle aboutTextStyle = themeData.textTheme.body2; final TextStyle linkStyle = themeData.textTheme.body2.copyWith(color: themeData.accentColor); final Widget lightThemeItem = new RadioListTile<bool>( secondary: const Icon(Icons.brightness_5), title: const Text('Light'), value: true, groupValue: useLightTheme, onChanged: onThemeChanged, selected: useLightTheme, ); final Widget darkThemeItem = new RadioListTile<bool>( secondary: const Icon(Icons.brightness_7), title: const Text('Dark'), value: false, groupValue: useLightTheme, onChanged: onThemeChanged, selected: !useLightTheme, ); final Widget mountainViewItem = new RadioListTile<TargetPlatform>( // on iOS, we don't want to show an Android phone icon secondary: new Icon(defaultTargetPlatform == TargetPlatform.iOS ? Icons.star : Icons.phone_android), title: new Text(defaultTargetPlatform == TargetPlatform.iOS ? 'Mountain View' : 'Android'), value: TargetPlatform.android, groupValue: Theme.of(context).platform, onChanged: onPlatformChanged, selected: Theme.of(context).platform == TargetPlatform.android, ); final Widget cupertinoItem = new RadioListTile<TargetPlatform>( // on iOS, we don't want to show the iPhone icon secondary: new Icon(defaultTargetPlatform == TargetPlatform.iOS ? Icons.star_border : Icons.phone_iphone), title: new Text(defaultTargetPlatform == TargetPlatform.iOS ? 'Cupertino' : 'iOS'), value: TargetPlatform.iOS, groupValue: Theme.of(context).platform, onChanged: onPlatformChanged, selected: Theme.of(context).platform == TargetPlatform.iOS, ); final List<Widget> textSizeItems = <Widget>[]; final Map<double, String> textSizes = <double, String>{ null: 'System Default', 0.8: 'Small', 1.0: 'Normal', 1.3: 'Large', 2.0: 'Huge', }; for (double size in textSizes.keys) { textSizeItems.add(new RadioListTile<double>( secondary: const Icon(Icons.text_fields), title: new Text(textSizes[size]), value: size, groupValue: textScaleFactor, onChanged: onTextScaleFactorChanged, selected: textScaleFactor == size, )); } final Widget animateSlowlyItem = new CheckboxListTile( title: const Text('Animate Slowly'), value: timeDilation != 1.0, onChanged: (bool value) { onTimeDilationChanged(value ? 20.0 : 1.0); }, secondary: const Icon(Icons.hourglass_empty), selected: timeDilation != 1.0, ); final Widget sendFeedbackItem = new ListTile( leading: const Icon(Icons.report), title: const Text('Send feedback'), onTap: onSendFeedback ?? () { launch('https://github.com/flutter/flutter/issues/new'); }, ); final Widget aboutItem = new AboutListTile( icon: const FlutterLogo(), applicationVersion: 'April 2017 Preview', applicationIcon: const FlutterLogo(), applicationLegalese: '© 2017 The Chromium Authors', aboutBoxChildren: <Widget>[ new Padding( padding: const EdgeInsets.only(top: 24.0), child: new RichText( text: new TextSpan( children: <TextSpan>[ new TextSpan( style: aboutTextStyle, text: 'Flutter is an early-stage, open-source project to help developers' 'build high-performance, high-fidelity, mobile apps for ' '${defaultTargetPlatform == TargetPlatform.iOS ? 'multiple platforms' : 'iOS and Android'} ' 'from a single codebase. This gallery is a preview of ' "Flutter's many widgets, behaviors, animations, layouts, " 'and more. Learn more about Flutter at ' ), new LinkTextSpan( style: linkStyle, url: 'https://flutter.io' ), new TextSpan( style: aboutTextStyle, text: '.\n\nTo see the source code for this app, please visit the ' ), new LinkTextSpan( style: linkStyle, url: 'https://goo.gl/iv1p4G', text: 'flutter github repo' ), new TextSpan( style: aboutTextStyle, text: '.' ) ] ) ) ) ] ); final List<Widget> allDrawerItems = <Widget>[ new GalleryDrawerHeader(light: useLightTheme), lightThemeItem, darkThemeItem, const Divider(), mountainViewItem, cupertinoItem, const Divider(), ]; allDrawerItems.addAll(textSizeItems); allDrawerItems..addAll(<Widget>[ const Divider(), animateSlowlyItem, const Divider(), ]); bool addedOptionalItem = false; if (onCheckerboardOffscreenLayersChanged != null) { allDrawerItems.add(new CheckboxListTile( title: const Text('Checkerboard Offscreen Layers'), value: checkerboardOffscreenLayers, onChanged: onCheckerboardOffscreenLayersChanged, secondary: const Icon(Icons.assessment), selected: checkerboardOffscreenLayers, )); addedOptionalItem = true; } if (onCheckerboardRasterCacheImagesChanged != null) { allDrawerItems.add(new CheckboxListTile( title: const Text('Checkerboard Raster Cache Images'), value: checkerboardRasterCacheImages, onChanged: onCheckerboardRasterCacheImagesChanged, secondary: const Icon(Icons.assessment), selected: checkerboardRasterCacheImages, )); addedOptionalItem = true; } if (onShowPerformanceOverlayChanged != null) { allDrawerItems.add(new CheckboxListTile( title: const Text('Performance Overlay'), value: showPerformanceOverlay, onChanged: onShowPerformanceOverlayChanged, secondary: const Icon(Icons.assessment), selected: showPerformanceOverlay, )); addedOptionalItem = true; } if (addedOptionalItem) allDrawerItems.add(const Divider()); allDrawerItems.addAll(<Widget>[ sendFeedbackItem, aboutItem, ]); return new Drawer(child: new ListView(primary: false, children: allDrawerItems)); } }