blob: 68c72585151cfededae1fcc015b2d420cacde1e3 [file] [log] [blame]
// Copyright 2016 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 'package:flutter/material.dart';
enum _Location {
Barbados,
Bahamas,
Bermuda
}
typedef Widget DemoItemBodyBuilder<T>(DemoItem<T> item);
typedef String ValueToString<T>(T value);
class DualHeaderWithHint extends StatelessWidget {
const DualHeaderWithHint({
this.name,
this.value,
this.hint,
this.showHint
});
final String name;
final String value;
final String hint;
final bool showHint;
Widget _crossFade(Widget first, Widget second, bool isExpanded) {
return new AnimatedCrossFade(
firstChild: first,
secondChild: second,
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn,
crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
duration: const Duration(milliseconds: 200),
);
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final TextTheme textTheme = theme.textTheme;
return new Row(
children: <Widget>[
new Expanded(
flex: 2,
child: new Container(
margin: const EdgeInsets.only(left: 24.0),
child: new FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: new Text(
name,
style: textTheme.body1.copyWith(fontSize: 15.0),
),
),
),
),
new Expanded(
flex: 3,
child: new Container(
margin: const EdgeInsets.only(left: 24.0),
child: _crossFade(
new Text(value, style: textTheme.caption.copyWith(fontSize: 15.0)),
new Text(hint, style: textTheme.caption.copyWith(fontSize: 15.0)),
showHint
)
)
)
]
);
}
}
class CollapsibleBody extends StatelessWidget {
const CollapsibleBody({
this.margin = EdgeInsets.zero,
this.child,
this.onSave,
this.onCancel
});
final EdgeInsets margin;
final Widget child;
final VoidCallback onSave;
final VoidCallback onCancel;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final TextTheme textTheme = theme.textTheme;
return new Column(
children: <Widget>[
new Container(
margin: const EdgeInsets.only(
left: 24.0,
right: 24.0,
bottom: 24.0
) - margin,
child: new Center(
child: new DefaultTextStyle(
style: textTheme.caption.copyWith(fontSize: 15.0),
child: child
)
)
),
const Divider(height: 1.0),
new Container(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Container(
margin: const EdgeInsets.only(right: 8.0),
child: new FlatButton(
onPressed: onCancel,
child: const Text('CANCEL', style: const TextStyle(
color: Colors.black54,
fontSize: 15.0,
fontWeight: FontWeight.w500
))
)
),
new Container(
margin: const EdgeInsets.only(right: 8.0),
child: new FlatButton(
onPressed: onSave,
textTheme: ButtonTextTheme.accent,
child: const Text('SAVE')
)
)
]
)
)
]
);
}
}
class DemoItem<T> {
DemoItem({
this.name,
this.value,
this.hint,
this.builder,
this.valueToString
}) : textController = new TextEditingController(text: valueToString(value));
final String name;
final String hint;
final TextEditingController textController;
final DemoItemBodyBuilder<T> builder;
final ValueToString<T> valueToString;
T value;
bool isExpanded = false;
ExpansionPanelHeaderBuilder get headerBuilder {
return (BuildContext context, bool isExpanded) {
return new DualHeaderWithHint(
name: name,
value: valueToString(value),
hint: hint,
showHint: isExpanded
);
};
}
Widget build() => builder(this);
}
class ExpansionPanelsDemo extends StatefulWidget {
static const String routeName = '/material/expansion_panels';
@override
_ExpansionPanelsDemoState createState() => new _ExpansionPanelsDemoState();
}
class _ExpansionPanelsDemoState extends State<ExpansionPanelsDemo> {
List<DemoItem<dynamic>> _demoItems;
@override
void initState() {
super.initState();
_demoItems = <DemoItem<dynamic>>[
new DemoItem<String>(
name: 'Trip',
value: 'Caribbean cruise',
hint: 'Change trip name',
valueToString: (String value) => value,
builder: (DemoItem<String> item) {
void close() {
setState(() {
item.isExpanded = false;
});
}
return new Form(
child: new Builder(
builder: (BuildContext context) {
return new CollapsibleBody(
margin: const EdgeInsets.symmetric(horizontal: 16.0),
onSave: () { Form.of(context).save(); close(); },
onCancel: () { Form.of(context).reset(); close(); },
child: new Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: new TextFormField(
controller: item.textController,
decoration: new InputDecoration(
hintText: item.hint,
labelText: item.name,
),
onSaved: (String value) { item.value = value; },
),
),
);
},
),
);
},
),
new DemoItem<_Location>(
name: 'Location',
value: _Location.Bahamas,
hint: 'Select location',
valueToString: (_Location location) => location.toString().split('.')[1],
builder: (DemoItem<_Location> item) {
void close() {
setState(() {
item.isExpanded = false;
});
}
return new Form(
child: new Builder(
builder: (BuildContext context) {
return new CollapsibleBody(
onSave: () { Form.of(context).save(); close(); },
onCancel: () { Form.of(context).reset(); close(); },
child: new FormField<_Location>(
initialValue: item.value,
onSaved: (_Location result) { item.value = result; },
builder: (FormFieldState<_Location> field) {
return new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Radio<_Location>(
value: _Location.Bahamas,
groupValue: field.value,
onChanged: field.didChange,
),
const Text('Bahamas')
]
),
new Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Radio<_Location>(
value: _Location.Barbados,
groupValue: field.value,
onChanged: field.didChange,
),
const Text('Barbados')
]
),
new Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Radio<_Location>(
value: _Location.Bermuda,
groupValue: field.value,
onChanged: field.didChange,
),
const Text('Bermuda')
]
)
]
);
}
),
);
}
)
);
}
),
new DemoItem<double>(
name: 'Sun',
value: 80.0,
hint: 'Select sun level',
valueToString: (double amount) => '${amount.round()}',
builder: (DemoItem<double> item) {
void close() {
setState(() {
item.isExpanded = false;
});
}
return new Form(
child: new Builder(
builder: (BuildContext context) {
return new CollapsibleBody(
onSave: () { Form.of(context).save(); close(); },
onCancel: () { Form.of(context).reset(); close(); },
child: new FormField<double>(
initialValue: item.value,
onSaved: (double value) { item.value = value; },
builder: (FormFieldState<double> field) {
return new Slider(
min: 0.0,
max: 100.0,
divisions: 5,
activeColor: Colors.orange[100 + (field.value * 5.0).round()],
label: '${field.value.round()}',
value: field.value,
onChanged: field.didChange,
);
},
),
);
}
)
);
}
)
];
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: const Text('Expansion panels')),
body: new SingleChildScrollView(
child: new SafeArea(
top: false,
bottom: false,
child: new Container(
margin: const EdgeInsets.all(24.0),
child: new ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_demoItems[index].isExpanded = !isExpanded;
});
},
children: _demoItems.map((DemoItem<dynamic> item) {
return new ExpansionPanel(
isExpanded: item.isExpanded,
headerBuilder: item.headerBuilder,
body: item.build()
);
}).toList()
),
),
),
),
);
}
}