1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6
7import 'package:flutter/cupertino.dart';
8import 'package:flutter/foundation.dart' show defaultTargetPlatform;
9import 'package:flutter/material.dart';
10import 'package:flutter/scheduler.dart' show timeDilation;
11import 'package:scoped_model/scoped_model.dart';
12import 'package:url_launcher/url_launcher.dart';
13
14import '../demo/shrine/model/app_state_model.dart';
15import 'demos.dart';
16import 'home.dart';
17import 'options.dart';
18import 'scales.dart';
19import 'themes.dart';
20import 'updater.dart';
21
22class GalleryApp extends StatefulWidget {
23 const GalleryApp({
24 super.key,
25 this.updateUrlFetcher,
26 this.enablePerformanceOverlay = true,
27 this.enableRasterCacheImagesCheckerboard = true,
28 this.enableOffscreenLayersCheckerboard = true,
29 this.onSendFeedback,
30 this.testMode = false,
31 });
32
33 final UpdateUrlFetcher? updateUrlFetcher;
34 final bool enablePerformanceOverlay;
35 final bool enableRasterCacheImagesCheckerboard;
36 final bool enableOffscreenLayersCheckerboard;
37 final VoidCallback? onSendFeedback;
38 final bool testMode;
39
40 @override
41 State<GalleryApp> createState() => _GalleryAppState();
42}
43
44class _GalleryAppState extends State<GalleryApp> {
45 GalleryOptions? _options;
46 Timer? _timeDilationTimer;
47 late final AppStateModel model = AppStateModel()..loadProducts();
48
49 Map<String, WidgetBuilder> _buildRoutes() {
50 // For a different example of how to set up an application routing table
51 // using named routes, consider the example in the Navigator class documentation:
52 // https://api.flutter.dev/flutter/widgets/Navigator-class.html
53 return <String, WidgetBuilder>{
54 for (final GalleryDemo demo in kAllGalleryDemos) demo.routeName: demo.buildRoute,
55 };
56 }
57
58 @override
59 void initState() {
60 super.initState();
61 _options = GalleryOptions(
62 themeMode: ThemeMode.system,
63 textScaleFactor: kAllGalleryTextScaleValues[0],
64 visualDensity: kAllGalleryVisualDensityValues[0],
65 timeDilation: timeDilation,
66 platform: defaultTargetPlatform,
67 );
68 }
69
70 @override
71 void reassemble() {
72 _options = _options!.copyWith(platform: defaultTargetPlatform);
73 super.reassemble();
74 }
75
76 @override
77 void dispose() {
78 _timeDilationTimer?.cancel();
79 _timeDilationTimer = null;
80 super.dispose();
81 }
82
83 void _handleOptionsChanged(GalleryOptions newOptions) {
84 setState(() {
85 if (_options!.timeDilation != newOptions.timeDilation) {
86 _timeDilationTimer?.cancel();
87 _timeDilationTimer = null;
88 if (newOptions.timeDilation > 1.0) {
89 // We delay the time dilation change long enough that the user can see
90 // that UI has started reacting and then we slam on the brakes so that
91 // they see that the time is in fact now dilated.
92 _timeDilationTimer = Timer(const Duration(milliseconds: 150), () {
93 timeDilation = newOptions.timeDilation;
94 });
95 } else {
96 timeDilation = newOptions.timeDilation;
97 }
98 }
99
100 _options = newOptions;
101 });
102 }
103
104 Widget _applyTextScaleFactor(Widget child) {
105 return Builder(
106 builder: (BuildContext context) {
107 final double? textScaleFactor = _options!.textScaleFactor!.scale;
108 return MediaQuery.withClampedTextScaling(
109 minScaleFactor: textScaleFactor ?? 0.0,
110 maxScaleFactor: textScaleFactor ?? double.infinity,
111 child: child,
112 );
113 },
114 );
115 }
116
117 @override
118 Widget build(BuildContext context) {
119 Widget home = GalleryHome(
120 testMode: widget.testMode,
121 optionsPage: GalleryOptionsPage(
122 options: _options,
123 onOptionsChanged: _handleOptionsChanged,
124 onSendFeedback:
125 widget.onSendFeedback ??
126 () {
127 launchUrl(
128 Uri.parse('https://github.com/flutter/flutter/issues/new/choose'),
129 mode: LaunchMode.externalApplication,
130 );
131 },
132 ),
133 );
134
135 if (widget.updateUrlFetcher != null) {
136 home = Updater(updateUrlFetcher: widget.updateUrlFetcher!, child: home);
137 }
138
139 return ScopedModel<AppStateModel>(
140 model: model,
141 child: MaterialApp(
142 // The automatically applied scrollbars on desktop can cause a crash for
143 // demos where many scrollables are all attached to the same
144 // PrimaryScrollController. The gallery needs to be migrated before
145 // enabling this. https://github.com/flutter/gallery/issues/523
146 scrollBehavior: const MaterialScrollBehavior().copyWith(scrollbars: false),
147 theme: kLightGalleryTheme.copyWith(
148 platform: _options!.platform,
149 visualDensity: _options!.visualDensity!.visualDensity,
150 ),
151 darkTheme: kDarkGalleryTheme.copyWith(
152 platform: _options!.platform,
153 visualDensity: _options!.visualDensity!.visualDensity,
154 ),
155 themeMode: _options!.themeMode,
156 title: 'Flutter Gallery',
157 color: Colors.grey,
158 showPerformanceOverlay: _options!.showPerformanceOverlay,
159 checkerboardOffscreenLayers: _options!.showOffscreenLayersCheckerboard,
160 checkerboardRasterCacheImages: _options!.showRasterCacheImagesCheckerboard,
161 routes: _buildRoutes(),
162 builder: (BuildContext context, Widget? child) {
163 return Directionality(
164 textDirection: _options!.textDirection,
165 child: _applyTextScaleFactor(
166 // Specifically use a blank Cupertino theme here and do not transfer
167 // over the Material primary color etc except the brightness to
168 // showcase standard iOS looks.
169 Builder(
170 builder: (BuildContext context) {
171 return CupertinoTheme(
172 data: CupertinoThemeData(brightness: Theme.brightnessOf(context)),
173 child: child!,
174 );
175 },
176 ),
177 ),
178 );
179 },
180 home: home,
181 ),
182 );
183 }
184}
185