From e697e5523fc36bb29ca96d7f8e548c7f359e9470 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 13 Oct 2017 21:44:24 -0700 Subject: [PATCH] Add large text switching to Gallery app. (#12443) This lets us preview widgets in the gallery using small, normal, large, and HUGE text. Added selections to the main drawer for these options. Defaults to "normal", obviously. --- .../demo/material/scrollable_tabs_demo.dart | 2 +- examples/flutter_gallery/lib/gallery/app.dart | 45 ++++++-- .../flutter_gallery/lib/gallery/drawer.dart | 100 ++++++++++++------ .../flutter_gallery/lib/gallery/home.dart | 7 ++ .../flutter_gallery/test/drawer_test.dart | 100 ++++++++++++++++++ examples/flutter_gallery/test/smoke_test.dart | 19 +++- 6 files changed, 228 insertions(+), 45 deletions(-) create mode 100644 examples/flutter_gallery/test/drawer_test.dart diff --git a/examples/flutter_gallery/lib/demo/material/scrollable_tabs_demo.dart b/examples/flutter_gallery/lib/demo/material/scrollable_tabs_demo.dart index dc741b6b7a..e7c9092e1d 100644 --- a/examples/flutter_gallery/lib/demo/material/scrollable_tabs_demo.dart +++ b/examples/flutter_gallery/lib/demo/material/scrollable_tabs_demo.dart @@ -22,7 +22,7 @@ final List<_Page> _allPages = <_Page>[ new _Page(icon: Icons.android, text: 'ANDROID'), new _Page(icon: Icons.alarm, text: 'ALARM'), new _Page(icon: Icons.face, text: 'FACE'), - new _Page(icon: Icons.language, text: 'LANGAUGE'), + new _Page(icon: Icons.language, text: 'LANGUAGE'), ]; class ScrollableTabsDemo extends StatefulWidget { diff --git a/examples/flutter_gallery/lib/gallery/app.dart b/examples/flutter_gallery/lib/gallery/app.dart index e5d814b9e4..a07a707936 100644 --- a/examples/flutter_gallery/lib/gallery/app.dart +++ b/examples/flutter_gallery/lib/gallery/app.dart @@ -12,15 +12,6 @@ import 'home.dart'; import 'item.dart'; import 'updates.dart'; -final Map _kRoutes = new Map.fromIterable( - // For a different example of how to set up an application routing table, - // consider the Stocks example: - // https://github.com/flutter/flutter/blob/master/examples/stocks/lib/main.dart - kAllGalleryItems, - key: (GalleryItem item) => item.routeName, - value: (GalleryItem item) => item.buildRoute, -); - final ThemeData _kGalleryLightTheme = new ThemeData( brightness: Brightness.light, primarySwatch: Colors.blue, @@ -63,6 +54,9 @@ class GalleryAppState extends State { double _timeDilation = 1.0; TargetPlatform _platform; + // A null value indicates "use system default". + double _textScaleFactor; + Timer _timeDilationTimer; @override @@ -128,6 +122,12 @@ class GalleryAppState extends State { } }); }, + textScaleFactor: _textScaleFactor, + onTextScaleFactorChanged: (double value) { + setState(() { + _textScaleFactor = value; + }); + }, onSendFeedback: widget.onSendFeedback, ); @@ -138,6 +138,33 @@ class GalleryAppState extends State { ); } + final Map _kRoutes = new Map.fromIterable( + // For a different example of how to set up an application routing table + // using named routes, consider the example in the Navigator class documentation: + // https://docs.flutter.io/flutter/widgets/Navigator-class.html + kAllGalleryItems, + key: (GalleryItem item) => item.routeName, + value: (GalleryItem item) => + (BuildContext context) { + if (_textScaleFactor != null) { + return new MediaQuery( + data: new MediaQueryData(textScaleFactor: _textScaleFactor), + child: item.buildRoute(context), + ); + } else { + return item.buildRoute(context); + } + } + ); + + if (_textScaleFactor != null) { + home = new MediaQuery( + data: new MediaQueryData(textScaleFactor: _textScaleFactor), + child: home, + ); + } + return new MaterialApp( title: 'Flutter Gallery', color: Colors.grey, diff --git a/examples/flutter_gallery/lib/gallery/drawer.dart b/examples/flutter_gallery/lib/gallery/drawer.dart index 992b6a700f..14f2d660bc 100644 --- a/examples/flutter_gallery/lib/gallery/drawer.dart +++ b/examples/flutter_gallery/lib/gallery/drawer.dart @@ -108,6 +108,8 @@ class GalleryDrawer extends StatelessWidget { @required this.onThemeChanged, this.timeDilation, @required this.onTimeDilationChanged, + this.textScaleFactor, + this.onTextScaleFactorChanged, this.showPerformanceOverlay, this.onShowPerformanceOverlayChanged, this.checkerboardRasterCacheImages, @@ -126,6 +128,9 @@ class GalleryDrawer extends StatelessWidget { final double timeDilation; final ValueChanged onTimeDilationChanged; + final double textScaleFactor; + final ValueChanged onTextScaleFactorChanged; + final bool showPerformanceOverlay; final ValueChanged onShowPerformanceOverlayChanged; @@ -183,6 +188,25 @@ class GalleryDrawer extends StatelessWidget { selected: Theme.of(context).platform == TargetPlatform.iOS, ); + final List textSizeItems = []; + final Map textSizes = { + 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( + 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, @@ -214,11 +238,11 @@ class GalleryDrawer extends StatelessWidget { children: [ new TextSpan( style: aboutTextStyle, - text: "Flutter is an early-stage, open-source project to help " - "developers build high-performance, high-fidelity, mobile " - "apps for 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 " + text: 'Flutter is an early-stage, open-source project to help ' + 'developers build high-performance, high-fidelity, mobile ' + 'apps for 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, @@ -226,7 +250,7 @@ class GalleryDrawer extends StatelessWidget { ), new TextSpan( style: aboutTextStyle, - text: ".\n\nTo see the source code for this app, please visit the " + text: '.\n\nTo see the source code for this app, please visit the ' ), new LinkTextSpan( style: linkStyle, @@ -235,7 +259,7 @@ class GalleryDrawer extends StatelessWidget { ), new TextSpan( style: aboutTextStyle, - text: "." + text: '.' ) ] ) @@ -252,42 +276,58 @@ class GalleryDrawer extends StatelessWidget { mountainViewItem, cupertinoItem, const Divider(), - animateSlowlyItem, - // index 8, optional: Performance Overlay - sendFeedbackItem, - aboutItem ]; - if (onShowPerformanceOverlayChanged != null) { - allDrawerItems.insert(8, new CheckboxListTile( - title: const Text('Performance Overlay'), - value: showPerformanceOverlay, - onChanged: onShowPerformanceOverlayChanged, - secondary: const Icon(Icons.assessment), - selected: showPerformanceOverlay, - )); - } + allDrawerItems.addAll(textSizeItems); - if (onCheckerboardRasterCacheImagesChanged != null) { - allDrawerItems.insert(8, new CheckboxListTile( - title: const Text('Checkerboard Raster Cache Images'), - value: checkerboardRasterCacheImages, - onChanged: onCheckerboardRasterCacheImagesChanged, - secondary: const Icon(Icons.assessment), - selected: checkerboardRasterCacheImages, - )); - } + allDrawerItems..addAll([ + const Divider(), + animateSlowlyItem, + const Divider(), + ]); + bool addedOptionalItem = false; if (onCheckerboardOffscreenLayersChanged != null) { - allDrawerItems.insert(8, new CheckboxListTile( + 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([ + sendFeedbackItem, + aboutItem, + ]); + return new Drawer(child: new ListView(primary: false, children: allDrawerItems)); } } diff --git a/examples/flutter_gallery/lib/gallery/home.dart b/examples/flutter_gallery/lib/gallery/home.dart index 2356cd33c3..70e949f73b 100644 --- a/examples/flutter_gallery/lib/gallery/home.dart +++ b/examples/flutter_gallery/lib/gallery/home.dart @@ -68,6 +68,8 @@ class GalleryHome extends StatefulWidget { @required this.onThemeChanged, this.timeDilation, @required this.onTimeDilationChanged, + this.textScaleFactor, + this.onTextScaleFactorChanged, this.showPerformanceOverlay, this.onShowPerformanceOverlayChanged, this.checkerboardRasterCacheImages, @@ -86,6 +88,9 @@ class GalleryHome extends StatefulWidget { final double timeDilation; final ValueChanged onTimeDilationChanged; + final double textScaleFactor; + final ValueChanged onTextScaleFactorChanged; + final bool showPerformanceOverlay; final ValueChanged onShowPerformanceOverlayChanged; @@ -159,6 +164,8 @@ class GalleryHomeState extends State with SingleTickerProviderState onThemeChanged: widget.onThemeChanged, timeDilation: widget.timeDilation, onTimeDilationChanged: widget.onTimeDilationChanged, + textScaleFactor: widget.textScaleFactor, + onTextScaleFactorChanged: widget.onTextScaleFactorChanged, showPerformanceOverlay: widget.showPerformanceOverlay, onShowPerformanceOverlayChanged: widget.onShowPerformanceOverlayChanged, checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, diff --git a/examples/flutter_gallery/test/drawer_test.dart b/examples/flutter_gallery/test/drawer_test.dart new file mode 100644 index 0000000000..3eb42bd5d3 --- /dev/null +++ b/examples/flutter_gallery/test/drawer_test.dart @@ -0,0 +1,100 @@ +// Copyright 2017 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 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_gallery/gallery/app.dart'; + +void main() { + final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); + if (binding is LiveTestWidgetsFlutterBinding) + binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; + + testWidgets('Flutter Gallery drawer item test', (WidgetTester tester) async { + bool hasFeedback = false; + void mockOnSendFeedback() { + hasFeedback = true; + } + + await tester.pumpWidget(new GalleryApp(onSendFeedback: mockOnSendFeedback)); + await tester.pump(); // see https://github.com/flutter/flutter/issues/1865 + await tester.pump(); // triggers a frame + + final Finder finder = find.byWidgetPredicate((Widget widget) { + return widget is Tooltip && widget.message == 'Open navigation menu'; + }); + expect(finder, findsOneWidget); + + // Open drawer + await tester.tap(finder); + await tester.pump(); // start animation + await tester.pump(const Duration(seconds: 1)); // end animation + + MaterialApp app = find.byType(MaterialApp).evaluate().first.widget; + expect(app.theme.brightness, equals(Brightness.light)); + + // Change theme + await tester.tap(find.text('Dark')); + await tester.pump(); // start animation + await tester.pump(const Duration(seconds: 1)); // end animation + app = find.byType(MaterialApp).evaluate().first.widget; + expect(app.theme.brightness, equals(Brightness.dark)); + expect(app.theme.platform, equals(TargetPlatform.android)); + + // Change platform + await tester.tap(find.text('iOS')); + await tester.pump(); // start animation + await tester.pump(const Duration(seconds: 1)); // end animation + app = find.byType(MaterialApp).evaluate().first.widget; + expect(app.theme.platform, equals(TargetPlatform.iOS)); + + // Verify the font scale. + final Size origTextSize = tester.getSize(find.text("Small")); + expect(origTextSize, equals(const Size(176.0, 14.0))); + + // Switch font scale. + await tester.tap(find.text('Small')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. + final Size textSize = tester.getSize(find.text("Small")); + expect(textSize, equals(const Size(176.0, 11.0))); + + // Set font scale back to default. + await tester.tap(find.text('System Default')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. + final Size newTextSize = tester.getSize(find.text("Small")); + expect(newTextSize, equals(origTextSize)); + + // Scroll to the bottom of the menu. + await tester.drag(find.text('Small'), const Offset(0.0, -450.0)); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. + + // Test slow animations. + expect(timeDilation, equals(1.0)); + await tester.tap(find.text('Animate Slowly')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. + expect(timeDilation, greaterThan(1.0)); + + // Put back time dilation (so as not to throw off tests after this one). + await tester.tap(find.text('Animate Slowly')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. + expect(timeDilation, equals(1.0)); + + // Send feedback. + expect(hasFeedback, false); + await tester.tap(find.text('Send feedback')); + await tester.pump(); + expect(hasFeedback, true); + + // Close drawer + await tester.tap(find.byType(DrawerController)); + await tester.pump(); // start animation + await tester.pump(const Duration(seconds: 1)); // end animation + }); +} diff --git a/examples/flutter_gallery/test/smoke_test.dart b/examples/flutter_gallery/test/smoke_test.dart index 26f6965c76..197885bce5 100644 --- a/examples/flutter_gallery/test/smoke_test.dart +++ b/examples/flutter_gallery/test/smoke_test.dart @@ -139,22 +139,31 @@ Future runSmokeTest(WidgetTester tester) async { await tester.pump(); // Start opening drawer. await tester.pump(const Duration(seconds: 1)); // Wait until it's really opened. - // switch theme + // Switch theme. await tester.tap(find.text('Dark')); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. - // switch theme + // Switch theme. await tester.tap(find.text('Light')); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. - // scroll the 'Send feedback' item into view - await tester.drag(find.text('Light'), const Offset(0.0, -200.0)); + // Switch font scale. + await tester.tap(find.text('Small')); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. + // Switch font scale back to default. + await tester.tap(find.text('System Default')); await tester.pump(); await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. - // send feedback + // Scroll the 'Send feedback' item into view. + await tester.drag(find.text('Small'), const Offset(0.0, -450.0)); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); // Wait until it's changed. + + // Send feedback. expect(hasFeedback, false); await tester.tap(find.text('Send feedback')); await tester.pump();