mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
386 lines
12 KiB
Dart
386 lines
12 KiB
Dart
// Copyright 2018 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:async';
|
|
import 'dart:developer';
|
|
import 'dart:math' as math;
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'backdrop.dart';
|
|
import 'demos.dart';
|
|
|
|
const String _kGalleryAssetsPackage = 'flutter_gallery_assets';
|
|
const Color _kFlutterBlue = const Color(0xFF003D75);
|
|
const double _kDemoItemHeight = 64.0;
|
|
const Duration _kFrontLayerSwitchDuration = const Duration(milliseconds: 300);
|
|
|
|
class _FlutterLogo extends StatelessWidget {
|
|
const _FlutterLogo({ Key key }) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return new Center(
|
|
child: new Container(
|
|
width: 34.0,
|
|
height: 34.0,
|
|
decoration: const BoxDecoration(
|
|
image: const DecorationImage(
|
|
image: const AssetImage(
|
|
'white_logo/logo.png',
|
|
package: _kGalleryAssetsPackage,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CategoryItem extends StatelessWidget {
|
|
const _CategoryItem({
|
|
Key key,
|
|
this.category,
|
|
this.onTap,
|
|
}) : super (key: key);
|
|
|
|
final GalleryDemoCategory category;
|
|
final VoidCallback onTap;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final ThemeData theme = Theme.of(context);
|
|
final bool isDark = theme.brightness == Brightness.dark;
|
|
|
|
// This repaint boundary prevents the entire _CategoriesPage from being
|
|
// repainted when the button's ink splash animates.
|
|
return new RepaintBoundary(
|
|
child: new RawMaterialButton(
|
|
padding: EdgeInsets.zero,
|
|
splashColor: theme.primaryColor.withOpacity(0.12),
|
|
highlightColor: Colors.transparent,
|
|
onPressed: onTap,
|
|
child: new Column(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: <Widget>[
|
|
new Padding(
|
|
padding: const EdgeInsets.all(6.0),
|
|
child: new Icon(
|
|
category.icon,
|
|
size: 60.0,
|
|
color: isDark ? Colors.white : _kFlutterBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 10.0),
|
|
new Container(
|
|
height: 48.0,
|
|
alignment: Alignment.center,
|
|
child: new Text(
|
|
category.name,
|
|
textAlign: TextAlign.center,
|
|
style: theme.textTheme.subhead.copyWith(
|
|
fontFamily: 'GoogleSans',
|
|
color: isDark ? Colors.white : _kFlutterBlue,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CategoriesPage extends StatelessWidget {
|
|
const _CategoriesPage({
|
|
Key key,
|
|
this.categories,
|
|
this.onCategoryTap,
|
|
}) : super(key: key);
|
|
|
|
final Iterable<GalleryDemoCategory> categories;
|
|
final ValueChanged<GalleryDemoCategory> onCategoryTap;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
const double aspectRatio = 160.0 / 180.0;
|
|
final List<GalleryDemoCategory> categoriesList = categories.toList();
|
|
final int columnCount = (MediaQuery.of(context).orientation == Orientation.portrait) ? 2 : 3;
|
|
|
|
return new SingleChildScrollView(
|
|
key: const PageStorageKey<String>('categories'),
|
|
child: new LayoutBuilder(
|
|
builder: (BuildContext context, BoxConstraints constraints) {
|
|
final double columnWidth = constraints.biggest.width / columnCount.toDouble();
|
|
final double rowHeight = columnWidth * aspectRatio;
|
|
final int rowCount = (categories.length + columnCount - 1) ~/ columnCount;
|
|
|
|
// This repaint boundary prevents the inner contents of the front layer
|
|
// from repainting when the backdrop toggle triggers a repaint on the
|
|
// LayoutBuilder.
|
|
return new RepaintBoundary(
|
|
child: new Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: new List<Widget>.generate(rowCount, (int rowIndex) {
|
|
final int columnCountForRow = rowIndex == rowCount - 1
|
|
? categories.length - columnCount * math.max(0, rowCount - 1)
|
|
: columnCount;
|
|
|
|
return new Row(
|
|
children: new List<Widget>.generate(columnCountForRow, (int columnIndex) {
|
|
final int index = rowIndex * columnCount + columnIndex;
|
|
final GalleryDemoCategory category = categoriesList[index];
|
|
|
|
return new SizedBox(
|
|
width: columnWidth,
|
|
height: rowHeight,
|
|
child: new _CategoryItem(
|
|
category: category,
|
|
onTap: () {
|
|
onCategoryTap(category);
|
|
},
|
|
),
|
|
);
|
|
}),
|
|
);
|
|
}),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DemoItem extends StatelessWidget {
|
|
const _DemoItem({ Key key, this.demo }) : super(key: key);
|
|
|
|
final GalleryDemo demo;
|
|
|
|
void _launchDemo(BuildContext context) {
|
|
if (demo.routeName != null) {
|
|
Timeline.instantSync('Start Transition', arguments: <String, String>{
|
|
'from': '/',
|
|
'to': demo.routeName,
|
|
});
|
|
Navigator.pushNamed(context, demo.routeName);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final ThemeData theme = Theme.of(context);
|
|
final bool isDark = theme.brightness == Brightness.dark;
|
|
final double textScaleFactor = MediaQuery.of(context)?.textScaleFactor ?? 1.0;
|
|
|
|
final List<Widget> titleChildren = <Widget>[
|
|
new Text(
|
|
demo.title,
|
|
style: theme.textTheme.subhead.copyWith(
|
|
color: isDark ? Colors.white : const Color(0xFF202124),
|
|
),
|
|
),
|
|
];
|
|
if (demo.subtitle != null) {
|
|
titleChildren.add(
|
|
new Text(
|
|
demo.subtitle,
|
|
style: theme.textTheme.body1.copyWith(
|
|
color: isDark ? Colors.white : const Color(0xFF60646B)
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return new RawMaterialButton(
|
|
padding: EdgeInsets.zero,
|
|
splashColor: theme.primaryColor.withOpacity(0.12),
|
|
highlightColor: Colors.transparent,
|
|
onPressed: () {
|
|
_launchDemo(context);
|
|
},
|
|
child: new Container(
|
|
constraints: new BoxConstraints(minHeight: _kDemoItemHeight * textScaleFactor),
|
|
child: new Row(
|
|
children: <Widget>[
|
|
new Container(
|
|
width: 56.0,
|
|
height: 56.0,
|
|
alignment: Alignment.center,
|
|
child: new Icon(
|
|
demo.icon,
|
|
size: 24.0,
|
|
color: isDark ? Colors.white : _kFlutterBlue,
|
|
),
|
|
),
|
|
new Expanded(
|
|
child: new Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: titleChildren,
|
|
),
|
|
),
|
|
const SizedBox(width: 44.0),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DemosPage extends StatelessWidget {
|
|
const _DemosPage(this.category);
|
|
|
|
final GalleryDemoCategory category;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return new KeyedSubtree(
|
|
key: const ValueKey<String>('GalleryDemoList'), // So the tests can find this ListView
|
|
child: new ListView(
|
|
key: new PageStorageKey<String>(category.name),
|
|
padding: const EdgeInsets.only(top: 8.0),
|
|
children: kGalleryCategoryToDemos[category].map<Widget>((GalleryDemo demo) {
|
|
return new _DemoItem(demo: demo);
|
|
}).toList(),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class GalleryHome extends StatefulWidget {
|
|
// In checked mode our MaterialApp will show the default "debug" banner.
|
|
// Otherwise show the "preview" banner.
|
|
static bool showPreviewBanner = true;
|
|
|
|
const GalleryHome({
|
|
Key key,
|
|
this.optionsPage,
|
|
}) : super(key: key);
|
|
|
|
final Widget optionsPage;
|
|
|
|
@override
|
|
_GalleryHomeState createState() => new _GalleryHomeState();
|
|
}
|
|
|
|
class _GalleryHomeState extends State<GalleryHome> with SingleTickerProviderStateMixin {
|
|
static final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
|
AnimationController _controller;
|
|
GalleryDemoCategory _category;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = new AnimationController(
|
|
duration: const Duration(milliseconds: 600),
|
|
debugLabel: 'preview banner',
|
|
vsync: this,
|
|
)..forward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
static Widget _animatedSwitcherLayoutBuilder(List<Widget> children) {
|
|
return new Stack(
|
|
children: children,
|
|
alignment: Alignment.topLeft,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final ThemeData theme = Theme.of(context);
|
|
final bool isDark = theme.brightness == Brightness.dark;
|
|
|
|
const Curve switchOutCurve = const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn);
|
|
const Curve switchInCurve = const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn);
|
|
|
|
Widget home = new Scaffold(
|
|
key: _scaffoldKey,
|
|
backgroundColor: isDark ? _kFlutterBlue : theme.primaryColor,
|
|
body: new SafeArea(
|
|
bottom: false,
|
|
child: new WillPopScope(
|
|
onWillPop: () {
|
|
// Pop the category page if Android back button is pressed.
|
|
if (_category != null) {
|
|
setState(() => _category = null);
|
|
return new Future<bool>.value(false);
|
|
}
|
|
return new Future<bool>.value(true);
|
|
},
|
|
child: new Backdrop(
|
|
backTitle: const Text('Options'),
|
|
backLayer: widget.optionsPage,
|
|
frontAction: new AnimatedSwitcher(
|
|
duration: _kFrontLayerSwitchDuration,
|
|
switchOutCurve: switchOutCurve,
|
|
switchInCurve: switchInCurve,
|
|
layoutBuilder: _animatedSwitcherLayoutBuilder,
|
|
child: _category == null
|
|
? const _FlutterLogo()
|
|
: new IconButton(
|
|
icon: const BackButtonIcon(),
|
|
tooltip: 'Back',
|
|
onPressed: () => setState(() => _category = null),
|
|
),
|
|
),
|
|
frontTitle: new AnimatedSwitcher(
|
|
duration: _kFrontLayerSwitchDuration,
|
|
child: _category == null
|
|
? const Text('Flutter gallery')
|
|
: new Text(_category.name),
|
|
),
|
|
frontHeading: new Container(height: 24.0),
|
|
frontLayer: new AnimatedSwitcher(
|
|
duration: _kFrontLayerSwitchDuration,
|
|
switchOutCurve: switchOutCurve,
|
|
switchInCurve: switchInCurve,
|
|
layoutBuilder: _animatedSwitcherLayoutBuilder,
|
|
child: _category != null
|
|
? new _DemosPage(_category)
|
|
: new _CategoriesPage(
|
|
categories: kAllGalleryDemoCategories,
|
|
onCategoryTap: (GalleryDemoCategory category) {
|
|
setState(() => _category = category);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
assert(() {
|
|
GalleryHome.showPreviewBanner = false;
|
|
return true;
|
|
}());
|
|
|
|
if (GalleryHome.showPreviewBanner) {
|
|
home = new Stack(
|
|
fit: StackFit.expand,
|
|
children: <Widget>[
|
|
home,
|
|
new FadeTransition(
|
|
opacity: new CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
|
|
child: const Banner(
|
|
message: 'PREVIEW',
|
|
location: BannerLocation.topEnd,
|
|
)
|
|
),
|
|
]
|
|
);
|
|
}
|
|
|
|
return home;
|
|
}
|
|
}
|