mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

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>
514 lines
14 KiB
Dart
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()));
|
|
}
|