flutter/dev/manual_tests/lib/card_collection.dart
Ian Hickson 449f4a6673
License update (#45373)
* Update project.pbxproj files to say Flutter rather than Chromium

Also, the templates now have an empty organization so that we don't cause people to give their apps a Flutter copyright.

* Update the copyright notice checker to require a standard notice on all files

* Update copyrights on Dart files. (This was a mechanical commit.)

* Fix weird license headers on Dart files that deviate from our conventions; relicense Shrine.

Some were already marked "The Flutter Authors", not clear why. Their
dates have been normalized. Some were missing the blank line after the
license. Some were randomly different in trivial ways for no apparent
reason (e.g. missing the trailing period).

* Clean up the copyrights in non-Dart files. (Manual edits.)

Also, make sure templates don't have copyrights.

* Fix some more ORGANIZATIONNAMEs
2019-11-27 15:04:02 -08:00

398 lines
13 KiB
Dart

// Copyright 2014 The Flutter 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';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart' show debugDumpRenderTree;
class CardModel {
CardModel(this.value, this.height) {
textController = TextEditingController(text: 'Item $value');
}
int value;
double height;
int get color => ((value % 9) + 1) * 100;
TextEditingController textController;
Key get key => ObjectKey(this);
}
class CardCollection extends StatefulWidget {
@override
CardCollectionState createState() => CardCollectionState();
}
class CardCollectionState extends State<CardCollection> {
static const TextStyle cardLabelStyle =
TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: FontWeight.bold);
// TODO(hansmuller): need a local image asset
static const String _sunshineURL = 'http://www.walltor.com/images/wallpaper/good-morning-sunshine-58540.jpg';
static const double kCardMargins = 8.0;
static const double kFixedCardHeight = 100.0;
static const List<double> _cardHeights = <double>[
48.0, 63.0, 85.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
48.0, 63.0, 85.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
48.0, 63.0, 85.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
];
MaterialColor _primaryColor = Colors.deepPurple;
List<CardModel> _cardModels;
DismissDirection _dismissDirection = DismissDirection.horizontal;
TextAlign _textAlign = TextAlign.center;
bool _editable = false;
bool _fixedSizeCards = false;
bool _sunshine = false;
bool _varyFontSizes = false;
void _updateCardSizes() {
if (_fixedSizeCards)
return;
_cardModels = List<CardModel>.generate(
_cardModels.length,
(int i) {
_cardModels[i].height = _editable ? max(_cardHeights[i], 60.0) : _cardHeights[i];
return _cardModels[i];
},
);
}
void _initVariableSizedCardModels() {
_cardModels = List<CardModel>.generate(
_cardHeights.length,
(int i) => CardModel(i, _editable ? max(_cardHeights[i], 60.0) : _cardHeights[i]),
);
}
void _initFixedSizedCardModels() {
const int cardCount = 27;
_cardModels = List<CardModel>.generate(
cardCount,
(int i) => CardModel(i, kFixedCardHeight),
);
}
void _initCardModels() {
if (_fixedSizeCards)
_initFixedSizedCardModels();
else
_initVariableSizedCardModels();
}
@override
void initState() {
super.initState();
_initCardModels();
}
void dismissCard(CardModel card) {
if (_cardModels.contains(card)) {
setState(() {
_cardModels.remove(card);
});
}
}
Widget _buildDrawer() {
return Drawer(
child: IconTheme(
data: const IconThemeData(color: Colors.black),
child: ListView(
children: <Widget>[
const DrawerHeader(child: Center(child: Text('Options'))),
buildDrawerCheckbox('Make card labels editable', _editable, _toggleEditable),
buildDrawerCheckbox('Fixed size cards', _fixedSizeCards, _toggleFixedSizeCards),
buildDrawerCheckbox('Let the sun shine', _sunshine, _toggleSunshine),
buildDrawerCheckbox('Vary font sizes', _varyFontSizes, _toggleVaryFontSizes, enabled: !_editable),
const Divider(),
buildDrawerColorRadioItem('Deep Purple', Colors.deepPurple, _primaryColor, _selectColor),
buildDrawerColorRadioItem('Green', Colors.green, _primaryColor, _selectColor),
buildDrawerColorRadioItem('Amber', Colors.amber, _primaryColor, _selectColor),
buildDrawerColorRadioItem('Teal', Colors.teal, _primaryColor, _selectColor),
const Divider(),
buildDrawerDirectionRadioItem('Dismiss horizontally', DismissDirection.horizontal, _dismissDirection, _changeDismissDirection, icon: Icons.code),
buildDrawerDirectionRadioItem('Dismiss left', DismissDirection.endToStart, _dismissDirection, _changeDismissDirection, icon: Icons.arrow_back),
buildDrawerDirectionRadioItem('Dismiss right', DismissDirection.startToEnd, _dismissDirection, _changeDismissDirection, icon: Icons.arrow_forward),
const Divider(),
buildFontRadioItem('Left-align text', TextAlign.left, _textAlign, _changeTextAlign, icon: Icons.format_align_left, enabled: !_editable),
buildFontRadioItem('Center-align text', TextAlign.center, _textAlign, _changeTextAlign, icon: Icons.format_align_center, enabled: !_editable),
buildFontRadioItem('Right-align text', TextAlign.right, _textAlign, _changeTextAlign, icon: Icons.format_align_right, enabled: !_editable),
const Divider(),
ListTile(
leading: const Icon(Icons.dvr),
onTap: () { debugDumpApp(); debugDumpRenderTree(); },
title: const Text('Dump App to Console'),
),
],
),
),
);
}
String _dismissDirectionText(DismissDirection direction) {
final String s = direction.toString();
return "dismiss ${s.substring(s.indexOf('.') + 1)}";
}
void _toggleEditable() {
setState(() {
_editable = !_editable;
_updateCardSizes();
});
}
void _toggleFixedSizeCards() {
setState(() {
_fixedSizeCards = !_fixedSizeCards;
_initCardModels();
});
}
void _toggleSunshine() {
setState(() {
_sunshine = !_sunshine;
});
}
void _toggleVaryFontSizes() {
setState(() {
_varyFontSizes = !_varyFontSizes;
});
}
void _selectColor(MaterialColor selection) {
setState(() {
_primaryColor = selection;
});
}
void _changeDismissDirection(DismissDirection newDismissDirection) {
setState(() {
_dismissDirection = newDismissDirection;
});
}
void _changeTextAlign(TextAlign newTextAlign) {
setState(() {
_textAlign = newTextAlign;
});
}
Widget buildDrawerCheckbox(String label, bool value, void callback(), { bool enabled = true }) {
return ListTile(
onTap: enabled ? callback : null,
title: Text(label),
trailing: Checkbox(
value: value,
onChanged: enabled ? (_) { callback(); } : null,
),
);
}
Widget buildDrawerColorRadioItem(String label, MaterialColor itemValue, MaterialColor currentValue, ValueChanged<MaterialColor> onChanged, { IconData icon, bool enabled = true }) {
return ListTile(
leading: Icon(icon),
title: Text(label),
onTap: enabled ? () { onChanged(itemValue); } : null,
trailing: Radio<MaterialColor>(
value: itemValue,
groupValue: currentValue,
onChanged: enabled ? onChanged : null,
),
);
}
Widget buildDrawerDirectionRadioItem(String label, DismissDirection itemValue, DismissDirection currentValue, ValueChanged<DismissDirection> onChanged, { IconData icon, bool enabled = true }) {
return ListTile(
leading: Icon(icon),
title: Text(label),
onTap: enabled ? () { onChanged(itemValue); } : null,
trailing: Radio<DismissDirection>(
value: itemValue,
groupValue: currentValue,
onChanged: enabled ? onChanged : null,
),
);
}
Widget buildFontRadioItem(String label, TextAlign itemValue, TextAlign currentValue, ValueChanged<TextAlign> onChanged, { IconData icon, bool enabled = true }) {
return ListTile(
leading: Icon(icon),
title: Text(label),
onTap: enabled ? () { onChanged(itemValue); } : null,
trailing: Radio<TextAlign>(
value: itemValue,
groupValue: currentValue,
onChanged: enabled ? onChanged : null,
),
);
}
Widget _buildAppBar(BuildContext context) {
return AppBar(
actions: <Widget>[
Text(_dismissDirectionText(_dismissDirection)),
],
flexibleSpace: Container(
padding: const EdgeInsets.only(left: 72.0),
height: 128.0,
alignment: const Alignment(-1.0, 0.5),
child: Text('Swipe Away: ${_cardModels.length}', style: Theme.of(context).primaryTextTheme.title),
),
);
}
Widget _buildCard(BuildContext context, int index) {
final CardModel cardModel = _cardModels[index];
final Widget card = Dismissible(
key: ObjectKey(cardModel),
direction: _dismissDirection,
onDismissed: (DismissDirection direction) { dismissCard(cardModel); },
child: Card(
color: _primaryColor[cardModel.color],
child: Container(
height: cardModel.height,
padding: const EdgeInsets.all(kCardMargins),
child: _editable ?
Center(
child: TextField(
key: GlobalObjectKey(cardModel),
controller: cardModel.textController,
),
)
: DefaultTextStyle.merge(
style: cardLabelStyle.copyWith(
fontSize: _varyFontSizes ? 5.0 + index : null
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(cardModel.textController.text, textAlign: _textAlign),
],
),
),
),
),
);
String backgroundMessage;
switch (_dismissDirection) {
case DismissDirection.horizontal:
backgroundMessage = 'Swipe in either direction';
break;
case DismissDirection.endToStart:
backgroundMessage = 'Swipe left to dismiss';
break;
case DismissDirection.startToEnd:
backgroundMessage = 'Swipe right to dismiss';
break;
default:
backgroundMessage = 'Unsupported dismissDirection';
}
// TODO(abarth): This icon is wrong in RTL.
Widget leftArrowIcon = const Icon(Icons.arrow_back, size: 36.0);
if (_dismissDirection == DismissDirection.startToEnd)
leftArrowIcon = Opacity(opacity: 0.1, child: leftArrowIcon);
// TODO(abarth): This icon is wrong in RTL.
Widget rightArrowIcon = const Icon(Icons.arrow_forward, size: 36.0);
if (_dismissDirection == DismissDirection.endToStart)
rightArrowIcon = Opacity(opacity: 0.1, child: rightArrowIcon);
final ThemeData theme = Theme.of(context);
final TextStyle backgroundTextStyle = theme.primaryTextTheme.title;
// The background Widget appears behind the Dismissible card when the card
// moves to the left or right. The Positioned widget ensures that the
// size of the background,card Stack will be based only on the card. The
// Viewport ensures that when the card's resize animation occurs, the
// background (text and icons) will just be clipped, not resized.
final Widget background = Positioned.fill(
child: Container(
margin: const EdgeInsets.all(4.0),
child: SingleChildScrollView(
child: Container(
height: cardModel.height,
color: theme.primaryColor,
child: Row(
children: <Widget>[
leftArrowIcon,
Expanded(
child: Text(backgroundMessage,
style: backgroundTextStyle,
textAlign: TextAlign.center,
),
),
rightArrowIcon,
],
),
),
),
),
);
return IconTheme(
key: cardModel.key,
data: const IconThemeData(color: Colors.white),
child: Stack(children: <Widget>[background, card]),
);
}
Shader _createShader(Rect bounds) {
return const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[Color(0x00FFFFFF), Color(0xFFFFFFFF)],
stops: <double>[0.1, 0.35],
)
.createShader(bounds);
}
@override
Widget build(BuildContext context) {
Widget cardCollection = ListView.builder(
itemExtent: _fixedSizeCards ? kFixedCardHeight : null,
itemCount: _cardModels.length,
itemBuilder: _buildCard,
);
if (_sunshine) {
cardCollection = Stack(
children: <Widget>[
Column(children: <Widget>[Image.network(_sunshineURL)]),
ShaderMask(child: cardCollection, shaderCallback: _createShader),
],
);
}
final Widget body = Container(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 8.0),
color: _primaryColor.shade50,
child: cardCollection,
);
return Theme(
data: ThemeData(
primarySwatch: _primaryColor,
),
child: Scaffold(
appBar: _buildAppBar(context),
drawer: _buildDrawer(),
body: body,
),
);
}
}
void main() {
runApp(MaterialApp(
title: 'Cards',
home: CardCollection(),
));
}