// Copyright 2018 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_web/material.dart'; import '../../gallery/demo.dart'; @visibleForTesting enum Location { Barbados, Bahamas, Bermuda } typedef DemoItemBodyBuilder = Widget Function(DemoItem item); typedef ValueToString = String Function(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 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 Row(children: [ Expanded( flex: 2, child: Container( margin: const EdgeInsets.only(left: 24.0), child: FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text( name, style: textTheme.body1.copyWith(fontSize: 15.0), ), ), ), ), Expanded( flex: 3, child: Container( margin: const EdgeInsets.only(left: 24.0), child: _crossFade( Text(value, style: textTheme.caption.copyWith(fontSize: 15.0)), 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 Column(children: [ Container( margin: const EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0) - margin, child: Center( child: DefaultTextStyle( style: textTheme.caption.copyWith(fontSize: 15.0), child: child))), const Divider(height: 1.0), Container( padding: const EdgeInsets.symmetric(vertical: 16.0), child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ Container( margin: const EdgeInsets.only(right: 8.0), child: FlatButton( onPressed: onCancel, child: const Text('CANCEL', style: TextStyle( color: Colors.black54, fontSize: 15.0, fontWeight: FontWeight.w500)))), Container( margin: const EdgeInsets.only(right: 8.0), child: FlatButton( onPressed: onSave, textTheme: ButtonTextTheme.accent, child: const Text('SAVE'))) ])) ]); } } class DemoItem { DemoItem({this.name, this.value, this.hint, this.builder, this.valueToString}) : textController = TextEditingController(text: valueToString(value)); final String name; final String hint; final TextEditingController textController; final DemoItemBodyBuilder builder; final ValueToString valueToString; T value; bool isExpanded = false; ExpansionPanelHeaderBuilder get headerBuilder { return (BuildContext context, bool isExpanded) { return 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() => _ExpansionPanelsDemoState(); } class _ExpansionPanelsDemoState extends State { List> _demoItems; @override void initState() { super.initState(); _demoItems = >[ DemoItem( name: 'Trip', value: 'Caribbean cruise', hint: 'Change trip name', valueToString: (String value) => value, builder: (DemoItem item) { void close() { setState(() { item.isExpanded = false; }); } return Form( child: Builder( builder: (BuildContext context) { return CollapsibleBody( margin: const EdgeInsets.symmetric(horizontal: 16.0), onSave: () { Form.of(context).save(); close(); }, onCancel: () { Form.of(context).reset(); close(); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: TextFormField( controller: item.textController, decoration: InputDecoration( hintText: item.hint, labelText: item.name, ), onSaved: (String value) { item.value = value; }, ), ), ); }, ), ); }, ), DemoItem( name: 'Location', value: Location.Bahamas, hint: 'Select location', valueToString: (Location location) => location.toString().split('.')[1], builder: (DemoItem item) { void close() { setState(() { item.isExpanded = false; }); } return Form(child: Builder(builder: (BuildContext context) { return CollapsibleBody( onSave: () { Form.of(context).save(); close(); }, onCancel: () { Form.of(context).reset(); close(); }, child: FormField( initialValue: item.value, onSaved: (Location result) { item.value = result; }, builder: (FormFieldState field) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ RadioListTile( value: Location.Bahamas, title: const Text('Bahamas'), groupValue: field.value, onChanged: field.didChange, ), RadioListTile( value: Location.Barbados, title: const Text('Barbados'), groupValue: field.value, onChanged: field.didChange, ), RadioListTile( value: Location.Bermuda, title: const Text('Bermuda'), groupValue: field.value, onChanged: field.didChange, ), ]); }), ); })); }), DemoItem( name: 'Sun', value: 80.0, hint: 'Select sun level', valueToString: (double amount) => '${amount.round()}', builder: (DemoItem item) { void close() { setState(() { item.isExpanded = false; }); } return Form(child: Builder(builder: (BuildContext context) { return CollapsibleBody( onSave: () { Form.of(context).save(); close(); }, onCancel: () { Form.of(context).reset(); close(); }, child: FormField( initialValue: item.value, onSaved: (double value) { item.value = value; }, builder: (FormFieldState field) { return 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 Scaffold( appBar: AppBar( title: const Text('Expansion panels'), actions: [ MaterialDemoDocumentationButton(ExpansionPanelsDemo.routeName), ], ), body: SingleChildScrollView( child: SafeArea( top: false, bottom: false, child: Container( margin: const EdgeInsets.all(24.0), child: ExpansionPanelList( expansionCallback: (int index, bool isExpanded) { setState(() { _demoItems[index].isExpanded = !isExpanded; }); }, children: _demoItems.map((DemoItem item) { return ExpansionPanel( isExpanded: item.isExpanded, headerBuilder: item.headerBuilder, body: item.build()); }).toList()), ), ), ), ); } }