flutter/dev/manual_tests/lib/card_collection.dart
Michael Goderbauer 5491c8c146
Auto-format Framework (#160545)
This auto-formats all *.dart files in the repository outside of the
`engine` subdirectory and enforces that these files stay formatted with
a presubmit check.

**Reviewers:** Please carefully review all the commits except for the
one titled "formatted". The "formatted" commit was auto-generated by
running `dev/tools/format.sh -a -f`. The other commits were hand-crafted
to prepare the repo for the formatting change. I recommend reviewing the
commits one-by-one via the "Commits" tab and avoiding Github's "Files
changed" tab as it will likely slow down your browser because of the
size of this PR.

---------

Co-authored-by: Kate Lovett <katelovett@google.com>
Co-authored-by: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com>
2024-12-19 20:06:21 +00:00

514 lines
14 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';
class CardModel {
CardModel(this.value, this.height) : textController = TextEditingController(text: 'Item $value');
int value;
double height;
int get color => ((value % 9) + 1) * 100;
final TextEditingController textController;
Key get key => ObjectKey(this);
}
class CardCollection extends StatefulWidget {
const CardCollection({super.key});
@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 = <CardModel>[];
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 Function() 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,
),
);
}
AppBar _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.titleLarge,
),
),
);
}
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';
case DismissDirection.endToStart:
backgroundMessage = 'Swipe left to dismiss';
case DismissDirection.startToEnd:
backgroundMessage = 'Swipe right to dismiss';
case DismissDirection.vertical:
case DismissDirection.up:
case DismissDirection.down:
case DismissDirection.none:
backgroundMessage = 'Unsupported dismissDirection';
}
// 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);
}
// 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.titleLarge;
// 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(shaderCallback: _createShader, child: cardCollection),
],
);
}
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(const MaterialApp(title: 'Cards', home: CardCollection()));
}