1
0
mirror of https://github.com/flutter/samples.git synced 2026-05-16 20:08:51 +00:00

[Gallery] Fix directory structure (#312)

This commit is contained in:
Pierre-Louis
2020-02-05 20:11:54 +01:00
committed by GitHub
parent 082592e9a9
commit cee267cf88
762 changed files with 12 additions and 12 deletions

View File

@@ -0,0 +1,67 @@
// Copyright 2019 The Flutter team. 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:gallery/data/gallery_options.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/studies/crane/backdrop.dart';
import 'package:gallery/studies/crane/eat_form.dart';
import 'package:gallery/studies/crane/fly_form.dart';
import 'package:gallery/studies/crane/sleep_form.dart';
import 'package:gallery/studies/crane/theme.dart';
class CraneApp extends StatefulWidget {
const CraneApp({Key key, this.navigatorKey}) : super(key: key);
final GlobalKey<NavigatorState> navigatorKey;
@override
_CraneAppState createState() => _CraneAppState();
}
class _CraneAppState extends State<CraneApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: widget.navigatorKey,
title: 'Crane',
debugShowCheckedModeBanner: false,
localizationsDelegates: GalleryLocalizations.localizationsDelegates,
supportedLocales: GalleryLocalizations.supportedLocales,
locale: GalleryOptions.of(context).locale,
initialRoute: '/',
onGenerateRoute: _getRoute,
theme: craneTheme.copyWith(
platform: GalleryOptions.of(context).platform,
),
darkTheme: craneTheme.copyWith(
platform: GalleryOptions.of(context).platform,
),
home: ApplyTextOptions(
child: Backdrop(
frontLayer: Container(),
backLayerItems: [
FlyForm(index: 0),
SleepForm(index: 1),
EatForm(index: 2),
],
frontTitle: Text('CRANE'),
backTitle: Text('MENU'),
),
),
);
}
}
Route<dynamic> _getRoute(RouteSettings settings) {
if (settings.name != '/') {
return null;
}
return MaterialPageRoute<void>(
settings: settings,
builder: (context) => CraneApp(),
fullscreenDialog: true,
);
}

View File

@@ -0,0 +1,281 @@
// Copyright 2019 The Flutter team. 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:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:meta/meta.dart';
import 'package:gallery/data/gallery_options.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/layout/adaptive.dart';
import 'package:gallery/studies/crane/border_tab_indicator.dart';
import 'package:gallery/studies/crane/backlayer.dart';
import 'package:gallery/studies/crane/colors.dart';
import 'package:gallery/studies/crane/header_form.dart';
import 'package:gallery/studies/crane/item_cards.dart';
class _FrontLayer extends StatelessWidget {
const _FrontLayer({
Key key,
this.title,
this.index,
}) : super(key: key);
final String title;
final int index;
static const frontLayerBorderRadius = 16.0;
@override
Widget build(BuildContext context) {
final isDesktop = isDisplayDesktop(context);
final isSmallDesktop = isDisplaySmallDesktop(context);
return DefaultFocusTraversal(
policy: ReadingOrderTraversalPolicy(),
child: PhysicalShape(
elevation: 16,
color: cranePrimaryWhite,
clipper: ShapeBorderClipper(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(frontLayerBorderRadius),
topRight: Radius.circular(frontLayerBorderRadius),
),
),
),
child: ListView(
padding: isDesktop
? EdgeInsets.symmetric(
horizontal:
isSmallDesktop ? appPaddingSmall : appPaddingLarge,
vertical: 22)
: EdgeInsets.all(20),
children: [
Text(title, style: Theme.of(context).textTheme.subtitle),
SizedBox(height: 20),
ItemCards(index: index),
],
),
),
);
}
}
/// Builds a Backdrop.
///
/// A Backdrop widget has two layers, front and back. The front layer is shown
/// by default, and slides down to show the back layer, from which a user
/// can make a selection. The user can also configure the titles for when the
/// front or back layer is showing.
class Backdrop extends StatefulWidget {
final Widget frontLayer;
final List<BackLayerItem> backLayerItems;
final Widget frontTitle;
final Widget backTitle;
const Backdrop({
@required this.frontLayer,
@required this.backLayerItems,
@required this.frontTitle,
@required this.backTitle,
}) : assert(frontLayer != null),
assert(backLayerItems != null),
assert(frontTitle != null),
assert(backTitle != null);
@override
_BackdropState createState() => _BackdropState();
}
class _BackdropState extends State<Backdrop> with TickerProviderStateMixin {
TabController _tabController;
Animation<Offset> _flyLayerOffset;
Animation<Offset> _sleepLayerOffset;
Animation<Offset> _eatLayerOffset;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
// Offsets to create a gap between front layers.
_flyLayerOffset = _tabController.animation
.drive(Tween<Offset>(begin: Offset(0, 0), end: Offset(-0.05, 0)));
_sleepLayerOffset = _tabController.animation
.drive(Tween<Offset>(begin: Offset(0.05, 0), end: Offset(0, 0)));
_eatLayerOffset = _tabController.animation
.drive(Tween<Offset>(begin: Offset(0.10, 0), end: Offset(0.05, 0)));
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
void _handleTabs(int tabIndex) {
_tabController.animateTo(tabIndex,
duration: const Duration(milliseconds: 300));
}
@override
Widget build(BuildContext context) {
final isDesktop = isDisplayDesktop(context);
final textScaleFactor = GalleryOptions.of(context).textScaleFactor(context);
return Material(
color: cranePurple800,
child: Padding(
padding: EdgeInsets.only(top: 12),
child: DefaultFocusTraversal(
policy: ReadingOrderTraversalPolicy(),
child: Scaffold(
backgroundColor: cranePurple800,
appBar: AppBar(
brightness: Brightness.dark,
elevation: 0,
titleSpacing: 0,
flexibleSpace: CraneAppBar(
tabController: _tabController,
tabHandler: _handleTabs,
),
),
body: FocusScope(
child: Stack(
children: [
BackLayer(
tabController: _tabController,
backLayerItems: widget.backLayerItems,
),
Container(
margin: EdgeInsets.only(
top: isDesktop
? (isDisplaySmallDesktop(context)
? textFieldHeight * 2
: textFieldHeight) +
20 * textScaleFactor / 2
: 175 + 140 * textScaleFactor / 2,
),
child: TabBarView(
physics: isDesktop
? NeverScrollableScrollPhysics()
: null, // use default TabBarView physics
controller: _tabController,
children: [
SlideTransition(
position: _flyLayerOffset,
child: _FrontLayer(
title: GalleryLocalizations.of(context)
.craneFlySubhead,
index: 0,
),
),
SlideTransition(
position: _sleepLayerOffset,
child: _FrontLayer(
title: GalleryLocalizations.of(context)
.craneSleepSubhead,
index: 1,
),
),
SlideTransition(
position: _eatLayerOffset,
child: _FrontLayer(
title: GalleryLocalizations.of(context)
.craneEatSubhead,
index: 2,
),
),
],
),
),
],
),
),
),
),
),
);
}
}
class CraneAppBar extends StatefulWidget {
final Function(int) tabHandler;
final TabController tabController;
const CraneAppBar({Key key, this.tabHandler, this.tabController})
: super(key: key);
@override
_CraneAppBarState createState() => _CraneAppBarState();
}
class _CraneAppBarState extends State<CraneAppBar> {
@override
Widget build(BuildContext context) {
final isDesktop = isDisplayDesktop(context);
final isSmallDesktop = isDisplaySmallDesktop(context);
final textScaleFactor = GalleryOptions.of(context).textScaleFactor(context);
return SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal:
isDesktop && !isSmallDesktop ? appPaddingLarge : appPaddingSmall,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ExcludeSemantics(
child: Image.asset(
'assets/crane/logo/logo.png',
fit: BoxFit.cover,
),
),
Expanded(
child: Padding(
padding: const EdgeInsetsDirectional.only(start: 24),
child: Theme(
data: Theme.of(context).copyWith(
splashColor: Colors.transparent,
),
child: TabBar(
indicator: BorderTabIndicator(
indicatorHeight: isDesktop ? 28 : 32,
textScaleFactor: textScaleFactor,
),
controller: widget.tabController,
labelPadding: isDesktop
? const EdgeInsets.symmetric(horizontal: 32)
: EdgeInsets.zero,
isScrollable: isDesktop, // left-align tabs on desktop
labelStyle: Theme.of(context).textTheme.button,
labelColor: cranePrimaryWhite,
unselectedLabelColor: cranePrimaryWhite.withOpacity(.6),
onTap: (index) => widget.tabController.animateTo(
index,
duration: const Duration(milliseconds: 300),
),
tabs: [
Tab(text: GalleryLocalizations.of(context).craneFly),
Tab(text: GalleryLocalizations.of(context).craneSleep),
Tab(text: GalleryLocalizations.of(context).craneEat),
],
),
),
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,49 @@
// Copyright 2019 The Flutter team. 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';
abstract class BackLayerItem extends StatefulWidget {
final int index;
BackLayerItem({Key key, this.index}) : super(key: key);
}
class BackLayer extends StatefulWidget {
final List<BackLayerItem> backLayerItems;
final TabController tabController;
const BackLayer({Key key, this.backLayerItems, this.tabController})
: super(key: key);
@override
_BackLayerState createState() => _BackLayerState();
}
class _BackLayerState extends State<BackLayer> {
@override
void initState() {
super.initState();
widget.tabController.addListener(() => setState(() {}));
}
@override
Widget build(BuildContext context) {
final tabIndex = widget.tabController.index;
return DefaultFocusTraversal(
policy: WidgetOrderFocusTraversalPolicy(),
child: IndexedStack(
index: tabIndex,
children: [
for (BackLayerItem backLayerItem in widget.backLayerItems)
Focus(
canRequestFocus: backLayerItem.index == tabIndex,
debugLabel: 'backLayerItem: $backLayerItem',
child: backLayerItem,
)
],
),
);
}
}

View File

@@ -0,0 +1,52 @@
// Copyright 2019 The Flutter team. 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/widgets.dart';
class BorderTabIndicator extends Decoration {
BorderTabIndicator({this.indicatorHeight, this.textScaleFactor}) : super();
final double indicatorHeight;
final double textScaleFactor;
@override
_BorderPainter createBoxPainter([VoidCallback onChanged]) {
return _BorderPainter(
this, indicatorHeight, this.textScaleFactor, onChanged);
}
}
class _BorderPainter extends BoxPainter {
_BorderPainter(
this.decoration,
this.indicatorHeight,
this.textScaleFactor,
VoidCallback onChanged,
) : assert(decoration != null),
assert(indicatorHeight >= 0),
super(onChanged);
final BorderTabIndicator decoration;
final double indicatorHeight;
final double textScaleFactor;
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
assert(configuration != null);
assert(configuration.size != null);
final horizontalInset = 16 - 4 * textScaleFactor;
final rect = Offset(offset.dx + horizontalInset,
(configuration.size.height / 2) - indicatorHeight / 2 - 1) &
Size(configuration.size.width - 2 * horizontalInset, indicatorHeight);
final paint = Paint();
paint.color = Colors.white;
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 2;
canvas.drawRRect(
RRect.fromRectAndRadius(rect, Radius.circular(56)),
paint,
);
}
}

View File

@@ -0,0 +1,20 @@
// Copyright 2019 The Flutter team. 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';
const cranePurple700 = Color(0xFF720D5D);
const cranePurple800 = Color(0xFF5D1049);
const cranePurple900 = Color(0xFF4E0D3A);
const craneRed700 = Color(0xFFE30425);
const craneWhite60 = Color(0x99FFFFFF);
const cranePrimaryWhite = Color(0xFFFFFFFF);
const craneErrorOrange = Color(0xFFFF9100);
const craneAlpha = Color(0x00FFFFFF);
const craneGrey = Color(0xFF747474);
const craneBlack = Color(0XFF1E252D);

View File

@@ -0,0 +1,51 @@
// Copyright 2019 The Flutter team. 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:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/studies/crane/backlayer.dart';
import 'package:gallery/studies/crane/header_form.dart';
class EatForm extends BackLayerItem {
EatForm({int index}) : super(index: index);
@override
_EatFormState createState() => _EatFormState();
}
class _EatFormState extends State<EatForm> {
final dinerController = TextEditingController();
final dateController = TextEditingController();
final timeController = TextEditingController();
final locationController = TextEditingController();
@override
Widget build(BuildContext context) {
return HeaderForm(
fields: <HeaderFormField>[
HeaderFormField(
iconData: Icons.person,
title: GalleryLocalizations.of(context).craneFormDiners,
textController: dinerController,
),
HeaderFormField(
iconData: Icons.date_range,
title: GalleryLocalizations.of(context).craneFormDate,
textController: dateController,
),
HeaderFormField(
iconData: Icons.access_time,
title: GalleryLocalizations.of(context).craneFormTime,
textController: timeController,
),
HeaderFormField(
iconData: Icons.restaurant_menu,
title: GalleryLocalizations.of(context).craneFormLocation,
textController: locationController,
),
],
);
}
}

View File

@@ -0,0 +1,51 @@
// Copyright 2019 The Flutter team. 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:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/studies/crane/backlayer.dart';
import 'package:gallery/studies/crane/header_form.dart';
class FlyForm extends BackLayerItem {
FlyForm({int index}) : super(index: index);
@override
_FlyFormState createState() => _FlyFormState();
}
class _FlyFormState extends State<FlyForm> {
final travelerController = TextEditingController();
final countryDestinationController = TextEditingController();
final destinationController = TextEditingController();
final dateController = TextEditingController();
@override
Widget build(BuildContext context) {
return HeaderForm(
fields: <HeaderFormField>[
HeaderFormField(
iconData: Icons.person,
title: GalleryLocalizations.of(context).craneFormTravelers,
textController: travelerController,
),
HeaderFormField(
iconData: Icons.place,
title: GalleryLocalizations.of(context).craneFormOrigin,
textController: countryDestinationController,
),
HeaderFormField(
iconData: Icons.airplanemode_active,
title: GalleryLocalizations.of(context).craneFormDestination,
textController: destinationController,
),
HeaderFormField(
iconData: Icons.date_range,
title: GalleryLocalizations.of(context).craneFormDates,
textController: dateController,
),
],
);
}
}

View File

@@ -0,0 +1,102 @@
// Copyright 2019 The Flutter team. 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:gallery/layout/adaptive.dart';
import 'package:gallery/studies/crane/colors.dart';
const textFieldHeight = 60.0;
const appPaddingLarge = 120.0;
const appPaddingSmall = 24.0;
class HeaderFormField {
final IconData iconData;
final String title;
final TextEditingController textController;
const HeaderFormField({this.iconData, this.title, this.textController});
}
class HeaderForm extends StatelessWidget {
final List<HeaderFormField> fields;
const HeaderForm({Key key, this.fields}) : super(key: key);
@override
Widget build(BuildContext context) {
final isDesktop = isDisplayDesktop(context);
final isSmallDesktop = isDisplaySmallDesktop(context);
return Padding(
padding: EdgeInsets.symmetric(
horizontal:
isDesktop && !isSmallDesktop ? appPaddingLarge : appPaddingSmall,
),
child: isDesktop
? LayoutBuilder(builder: (context, constraints) {
var crossAxisCount = isSmallDesktop ? 2 : 4;
if (fields.length < crossAxisCount) {
crossAxisCount = fields.length;
}
final itemWidth = constraints.maxWidth / crossAxisCount;
return GridView.count(
crossAxisCount: crossAxisCount,
childAspectRatio: itemWidth / textFieldHeight,
physics: NeverScrollableScrollPhysics(),
children: [
for (final field in fields)
Padding(
padding: const EdgeInsetsDirectional.only(end: 16),
child: _HeaderTextField(field: field),
)
],
);
})
: Column(
mainAxisSize: MainAxisSize.min,
children: [
for (final field in fields)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: _HeaderTextField(field: field),
)
],
),
);
}
}
class _HeaderTextField extends StatelessWidget {
final HeaderFormField field;
_HeaderTextField({this.field});
@override
Widget build(BuildContext context) {
return TextField(
controller: field.textController,
cursorColor: Theme.of(context).colorScheme.secondary,
style: Theme.of(context).textTheme.body2.copyWith(color: Colors.white),
onTap: () {},
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4),
borderSide: BorderSide(
width: 0,
style: BorderStyle.none,
),
),
contentPadding: EdgeInsets.all(16),
fillColor: cranePurple700,
filled: true,
hintText: field.title,
hasFloatingPlaceholder: false,
prefixIcon: Icon(
field.iconData,
size: 24,
color: Theme.of(context).iconTheme.color,
),
),
);
}
}

View File

@@ -0,0 +1,179 @@
// Copyright 2019 The Flutter team. 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/rendering.dart';
import 'package:gallery/layout/adaptive.dart';
import 'package:gallery/layout/highlight_focus.dart';
import 'package:gallery/studies/crane/model/data.dart';
import 'package:gallery/studies/crane/model/destination.dart';
class ItemCards extends StatefulWidget {
final int index;
const ItemCards({Key key, this.index}) : super(key: key);
static const totalColumns = 4;
@override
_ItemCardsState createState() => _ItemCardsState();
}
class _ItemCardsState extends State<ItemCards> {
List<Destination> flyDestinations;
List<Destination> sleepDestinations;
List<Destination> eatDestinations;
List<Widget> _buildDestinationCards({int listIndex}) {
final List<Destination> destinations = [
if (listIndex == 0) ...flyDestinations,
if (listIndex == 1) ...sleepDestinations,
if (listIndex == 2) ...eatDestinations,
];
return destinations
.map(
(d) => HighlightFocus(
debugLabel: 'DestinationCard: ${d.destination}',
highlightColor: Colors.red.withOpacity(0.5),
onPressed: () {},
child: RepaintBoundary(
child: _DestinationCard(destination: d),
),
),
)
.toList();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// We use didChangeDependencies because the initialization involves an
// InheritedWidget (for localization). However, we don't need to get
// destinations again when, say, resizing the window.
if (flyDestinations == null) {
flyDestinations = getFlyDestinations(context);
sleepDestinations = getSleepDestinations(context);
eatDestinations = getEatDestinations(context);
}
}
@override
Widget build(BuildContext context) {
final isDesktop = isDisplayDesktop(context);
final List<Widget> destinationCards =
_buildDestinationCards(listIndex: widget.index);
if (isDesktop) {
var columns = List<List<Widget>>(ItemCards.totalColumns);
for (var i = 0; i < destinationCards.length; i++) {
final col = i % ItemCards.totalColumns;
if (columns[col] == null) {
columns[col] = List<Widget>();
}
columns[col].add(
// TODO: determine why this is isn't always respected
Semantics(
sortKey: OrdinalSortKey(i.toDouble(), name: 'destination'),
child: destinationCards[i],
),
);
}
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (var column in columns)
Expanded(
child: Padding(
padding: const EdgeInsetsDirectional.only(end: 16),
child: Column(
children: column,
),
),
)
],
);
} else {
return Column(children: destinationCards);
}
}
}
class _DestinationCard extends StatelessWidget {
_DestinationCard({this.destination}) : assert(destination != null);
final Destination destination;
@override
Widget build(BuildContext context) {
final imageWidget = Semantics(
child: ExcludeSemantics(
child: Image.asset(
destination.assetName,
fit: BoxFit.cover,
),
),
label: destination.assetSemanticLabel,
);
final isDesktop = isDisplayDesktop(context);
final textTheme = Theme.of(context).textTheme;
if (isDesktop) {
return Padding(
padding: const EdgeInsets.only(bottom: 40),
child: Semantics(
container: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(4)),
child: imageWidget,
),
Padding(
padding: const EdgeInsets.only(top: 20, bottom: 10),
child: Text(
destination.destination,
style: textTheme.subhead,
),
),
Text(
destination.subtitle(context),
semanticsLabel: destination.subtitleSemantics(context),
style: textTheme.subtitle,
),
],
),
),
);
} else {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
contentPadding: EdgeInsetsDirectional.only(end: 8),
leading: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(4)),
child: SizedBox(
height: 60,
width: 60,
child: imageWidget,
),
),
title: Text(destination.destination, style: textTheme.subhead),
subtitle: Text(
destination.subtitle(context),
semanticsLabel: destination.subtitleSemantics(context),
style: textTheme.subtitle,
),
),
Divider(),
],
);
}
}
}

View File

@@ -0,0 +1,294 @@
// Copyright 2019 The Flutter team. 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:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/studies/crane/model/destination.dart';
// TODO: localize durations
List<FlyDestination> getFlyDestinations(BuildContext context) =>
<FlyDestination>[
FlyDestination(
id: 0,
destination: GalleryLocalizations.of(context).craneFly0,
stops: 1,
duration: Duration(hours: 6, minutes: 15),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly0SemanticLabel,
),
FlyDestination(
id: 1,
destination: GalleryLocalizations.of(context).craneFly1,
stops: 0,
duration: Duration(hours: 13, minutes: 30),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly1SemanticLabel,
),
FlyDestination(
id: 2,
destination: GalleryLocalizations.of(context).craneFly2,
stops: 0,
duration: Duration(hours: 5, minutes: 16),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly2SemanticLabel,
),
FlyDestination(
id: 3,
destination: GalleryLocalizations.of(context).craneFly3,
stops: 2,
duration: Duration(hours: 19, minutes: 40),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly3SemanticLabel,
),
FlyDestination(
id: 4,
destination: GalleryLocalizations.of(context).craneFly4,
stops: 0,
duration: Duration(hours: 8, minutes: 24),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly4SemanticLabel,
),
FlyDestination(
id: 5,
destination: GalleryLocalizations.of(context).craneFly5,
stops: 1,
duration: Duration(hours: 14, minutes: 12),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly5SemanticLabel,
),
FlyDestination(
id: 6,
destination: GalleryLocalizations.of(context).craneFly6,
stops: 0,
duration: Duration(hours: 5, minutes: 24),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly6SemanticLabel,
),
FlyDestination(
id: 7,
destination: GalleryLocalizations.of(context).craneFly7,
stops: 1,
duration: Duration(hours: 5, minutes: 43),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly7SemanticLabel,
),
FlyDestination(
id: 8,
destination: GalleryLocalizations.of(context).craneFly8,
stops: 0,
duration: Duration(hours: 8, minutes: 25),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly8SemanticLabel,
),
FlyDestination(
id: 9,
destination: GalleryLocalizations.of(context).craneFly9,
stops: 1,
duration: Duration(hours: 15, minutes: 52),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly9SemanticLabel,
),
FlyDestination(
id: 10,
destination: GalleryLocalizations.of(context).craneFly10,
stops: 0,
duration: Duration(hours: 5, minutes: 57),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly10SemanticLabel,
),
FlyDestination(
id: 11,
destination: GalleryLocalizations.of(context).craneFly11,
stops: 1,
duration: Duration(hours: 13, minutes: 24),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly11SemanticLabel,
),
FlyDestination(
id: 12,
destination: GalleryLocalizations.of(context).craneFly12,
stops: 2,
duration: Duration(hours: 10, minutes: 20),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly12SemanticLabel,
),
FlyDestination(
id: 13,
destination: GalleryLocalizations.of(context).craneFly13,
stops: 0,
duration: Duration(hours: 7, minutes: 15),
assetSemanticLabel:
GalleryLocalizations.of(context).craneFly13SemanticLabel,
),
];
List<SleepDestination> getSleepDestinations(BuildContext context) =>
<SleepDestination>[
SleepDestination(
id: 0,
destination: GalleryLocalizations.of(context).craneSleep0,
total: 2241,
assetSemanticLabel:
GalleryLocalizations.of(context).craneSleep0SemanticLabel,
),
SleepDestination(
id: 1,
destination: GalleryLocalizations.of(context).craneSleep1,
total: 876,
assetSemanticLabel:
GalleryLocalizations.of(context).craneSleep1SemanticLabel,
),
SleepDestination(
id: 2,
destination: GalleryLocalizations.of(context).craneSleep2,
total: 1286,
assetSemanticLabel:
GalleryLocalizations.of(context).craneSleep2SemanticLabel,
),
SleepDestination(
id: 3,
destination: GalleryLocalizations.of(context).craneSleep3,
total: 496,
assetSemanticLabel:
GalleryLocalizations.of(context).craneSleep3SemanticLabel,
),
SleepDestination(
id: 4,
destination: GalleryLocalizations.of(context).craneSleep4,
total: 390,
assetSemanticLabel:
GalleryLocalizations.of(context).craneSleep4SemanticLabel,
),
SleepDestination(
id: 5,
destination: GalleryLocalizations.of(context).craneSleep5,
total: 876,
assetSemanticLabel:
GalleryLocalizations.of(context).craneSleep5SemanticLabel,
),
SleepDestination(
id: 6,
destination: GalleryLocalizations.of(context).craneSleep6,
total: 989,
assetSemanticLabel:
GalleryLocalizations.of(context).craneSleep6SemanticLabel,
),
SleepDestination(
id: 7,
destination: GalleryLocalizations.of(context).craneSleep7,
total: 306,
assetSemanticLabel:
GalleryLocalizations.of(context).craneSleep7SemanticLabel,
),
SleepDestination(
id: 8,
destination: GalleryLocalizations.of(context).craneSleep8,
total: 385,
assetSemanticLabel:
GalleryLocalizations.of(context).craneSleep8SemanticLabel,
),
SleepDestination(
id: 9,
destination: GalleryLocalizations.of(context).craneSleep9,
total: 989,
assetSemanticLabel:
GalleryLocalizations.of(context).craneSleep9SemanticLabel,
),
SleepDestination(
id: 10,
destination: GalleryLocalizations.of(context).craneSleep10,
total: 1380,
assetSemanticLabel:
GalleryLocalizations.of(context).craneSleep10SemanticLabel,
),
SleepDestination(
id: 11,
destination: GalleryLocalizations.of(context).craneSleep11,
total: 1109,
assetSemanticLabel:
GalleryLocalizations.of(context).craneSleep11SemanticLabel,
),
];
List<EatDestination> getEatDestinations(BuildContext context) =>
<EatDestination>[
EatDestination(
id: 0,
destination: GalleryLocalizations.of(context).craneEat0,
total: 354,
assetSemanticLabel:
GalleryLocalizations.of(context).craneEat0SemanticLabel,
),
EatDestination(
id: 1,
destination: GalleryLocalizations.of(context).craneEat1,
total: 623,
assetSemanticLabel:
GalleryLocalizations.of(context).craneEat1SemanticLabel,
),
EatDestination(
id: 2,
destination: GalleryLocalizations.of(context).craneEat2,
total: 124,
assetSemanticLabel:
GalleryLocalizations.of(context).craneEat2SemanticLabel,
),
EatDestination(
id: 3,
destination: GalleryLocalizations.of(context).craneEat3,
total: 495,
assetSemanticLabel:
GalleryLocalizations.of(context).craneEat3SemanticLabel,
),
EatDestination(
id: 4,
destination: GalleryLocalizations.of(context).craneEat4,
total: 683,
assetSemanticLabel:
GalleryLocalizations.of(context).craneEat4SemanticLabel,
),
EatDestination(
id: 5,
destination: GalleryLocalizations.of(context).craneEat5,
total: 786,
assetSemanticLabel:
GalleryLocalizations.of(context).craneEat5SemanticLabel,
),
EatDestination(
id: 6,
destination: GalleryLocalizations.of(context).craneEat6,
total: 323,
assetSemanticLabel:
GalleryLocalizations.of(context).craneEat6SemanticLabel,
),
EatDestination(
id: 7,
destination: GalleryLocalizations.of(context).craneEat7,
total: 285,
assetSemanticLabel:
GalleryLocalizations.of(context).craneEat7SemanticLabel,
),
EatDestination(
id: 8,
destination: GalleryLocalizations.of(context).craneEat8,
total: 323,
assetSemanticLabel:
GalleryLocalizations.of(context).craneEat8SemanticLabel,
),
EatDestination(
id: 9,
destination: GalleryLocalizations.of(context).craneEat9,
total: 1406,
assetSemanticLabel:
GalleryLocalizations.of(context).craneEat9SemanticLabel,
),
EatDestination(
id: 10,
destination: GalleryLocalizations.of(context).craneEat10,
total: 849,
assetSemanticLabel:
GalleryLocalizations.of(context).craneEat10SemanticLabel,
),
];

View File

@@ -0,0 +1,127 @@
// Copyright 2019 The Flutter team. 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/foundation.dart';
import 'package:flutter/material.dart';
import 'package:gallery/data/gallery_options.dart';
import 'package:gallery/studies/crane/model/formatters.dart';
import '../../../l10n/gallery_localizations.dart';
abstract class Destination {
const Destination({
@required this.id,
@required this.destination,
@required this.assetSemanticLabel,
}) : assert(id != null),
assert(destination != null);
final int id;
final String destination;
final String assetSemanticLabel;
String get assetName;
String subtitle(BuildContext context);
String subtitleSemantics(BuildContext context) => subtitle(context);
@override
String toString() => '$destination (id=$id)';
}
class FlyDestination extends Destination {
const FlyDestination({
@required int id,
@required String destination,
@required String assetSemanticLabel,
@required this.stops,
this.duration,
}) : assert(stops != null),
assert(destination != null),
super(
id: id,
destination: destination,
assetSemanticLabel: assetSemanticLabel,
);
final int stops;
final Duration duration;
String get assetName => 'assets/crane/destinations/fly_$id.jpg';
String subtitle(BuildContext context) {
final stopsText = GalleryLocalizations.of(context).craneFlyStops(stops);
if (duration == null) {
return stopsText;
} else {
final textDirection = GalleryOptions.of(context).textDirection();
final durationText =
formattedDuration(context, duration, abbreviated: true);
return textDirection == TextDirection.ltr
? '$stopsText · $durationText'
: '$durationText · $stopsText';
}
}
@override
String subtitleSemantics(BuildContext context) {
final stopsText = GalleryLocalizations.of(context).craneFlyStops(stops);
if (duration == null) {
return stopsText;
} else {
final durationText =
formattedDuration(context, duration, abbreviated: false);
return '$stopsText, $durationText';
}
}
}
class SleepDestination extends Destination {
const SleepDestination({
@required int id,
@required String destination,
@required String assetSemanticLabel,
@required this.total,
}) : assert(total != null),
assert(destination != null),
super(
id: id,
destination: destination,
assetSemanticLabel: assetSemanticLabel,
);
final int total;
String get assetName => 'assets/crane/destinations/sleep_$id.jpg';
String subtitle(BuildContext context) {
return GalleryLocalizations.of(context).craneSleepProperties(total);
}
}
class EatDestination extends Destination {
const EatDestination({
@required int id,
@required String destination,
@required String assetSemanticLabel,
@required this.total,
}) : assert(total != null),
assert(destination != null),
super(
id: id,
destination: destination,
assetSemanticLabel: assetSemanticLabel,
);
final int total;
String get assetName => 'assets/crane/destinations/eat_$id.jpg';
String subtitle(BuildContext context) {
return GalleryLocalizations.of(context).craneEatRestaurants(total);
}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2019 The Flutter team. 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:gallery/l10n/gallery_localizations.dart';
// Duration of time (e.g. 16h 12m)
String formattedDuration(BuildContext context, Duration duration,
{bool abbreviated}) {
final hoursShortForm =
GalleryLocalizations.of(context).craneHours(duration.inHours.toInt());
final minutesShortForm =
GalleryLocalizations.of(context).craneMinutes(duration.inMinutes % 60);
return GalleryLocalizations.of(context)
.craneFlightDuration(hoursShortForm, minutesShortForm);
}

View File

@@ -0,0 +1,45 @@
// Copyright 2019 The Flutter team. 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:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/studies/crane/backlayer.dart';
import 'package:gallery/studies/crane/header_form.dart';
class SleepForm extends BackLayerItem {
SleepForm({int index}) : super(index: index);
@override
_SleepFormState createState() => _SleepFormState();
}
class _SleepFormState extends State<SleepForm> {
final travelerController = TextEditingController();
final dateController = TextEditingController();
final locationController = TextEditingController();
@override
Widget build(BuildContext context) {
return HeaderForm(
fields: <HeaderFormField>[
HeaderFormField(
iconData: Icons.person,
title: GalleryLocalizations.of(context).craneFormTravelers,
textController: travelerController,
),
HeaderFormField(
iconData: Icons.date_range,
title: GalleryLocalizations.of(context).craneFormDates,
textController: dateController,
),
HeaderFormField(
iconData: Icons.hotel,
title: GalleryLocalizations.of(context).craneFormLocation,
textController: locationController,
),
],
);
}
}

View File

@@ -0,0 +1,106 @@
// Copyright 2019 The Flutter team. 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:google_fonts/google_fonts.dart';
import 'package:gallery/studies/crane/colors.dart';
final ThemeData craneTheme = _buildCraneTheme();
IconThemeData _customIconTheme(IconThemeData original, Color color) {
return original.copyWith(color: color);
}
ThemeData _buildCraneTheme() {
final ThemeData base = ThemeData.light();
return base.copyWith(
colorScheme: ColorScheme.light().copyWith(
primary: cranePurple800,
secondary: craneRed700,
),
accentColor: cranePurple700,
primaryColor: cranePurple800,
buttonColor: craneRed700,
hintColor: craneWhite60,
indicatorColor: cranePrimaryWhite,
scaffoldBackgroundColor: cranePrimaryWhite,
cardColor: cranePrimaryWhite,
textSelectionColor: cranePurple700,
errorColor: craneErrorOrange,
highlightColor: Colors.transparent,
buttonTheme: ButtonThemeData(
textTheme: ButtonTextTheme.accent,
),
textTheme: _buildCraneTextTheme(base.textTheme),
primaryTextTheme: _buildCraneTextTheme(base.primaryTextTheme),
accentTextTheme: _buildCraneTextTheme(base.accentTextTheme),
iconTheme: _customIconTheme(base.iconTheme, craneWhite60),
primaryIconTheme: _customIconTheme(base.iconTheme, cranePrimaryWhite),
);
}
TextTheme _buildCraneTextTheme(TextTheme base) {
return GoogleFonts.ralewayTextTheme(
base.copyWith(
display4: base.display4.copyWith(
fontWeight: FontWeight.w300,
fontSize: 96,
),
display3: base.display3.copyWith(
fontWeight: FontWeight.w400,
fontSize: 60,
),
display2: base.display2.copyWith(
fontWeight: FontWeight.w600,
fontSize: 48,
),
display1: base.display1.copyWith(
fontWeight: FontWeight.w600,
fontSize: 34,
),
headline: base.headline.copyWith(
fontWeight: FontWeight.w600,
fontSize: 24,
),
title: base.title.copyWith(
fontWeight: FontWeight.w600,
fontSize: 20,
),
subhead: base.subhead.copyWith(
fontWeight: FontWeight.w500,
fontSize: 16,
letterSpacing: 0.5,
),
subtitle: base.subtitle.copyWith(
fontWeight: FontWeight.w600,
fontSize: 12,
color: craneGrey,
),
body2: base.body2.copyWith(
fontWeight: FontWeight.w500,
fontSize: 16,
),
body1: base.body1.copyWith(
fontWeight: FontWeight.w400,
fontSize: 14,
),
button: base.button.copyWith(
fontWeight: FontWeight.w600,
fontSize: 13,
letterSpacing: 0.8,
),
caption: base.caption.copyWith(
fontWeight: FontWeight.w500,
fontSize: 12,
color: craneGrey,
),
overline: base.overline.copyWith(
fontWeight: FontWeight.w600,
fontSize: 12,
),
),
);
}