// 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'; @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: '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, ), ] ); } ), ); } ) ); } ), ]; } @override Widget build(BuildContext context) { return 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: true, headerBuilder: item.headerBuilder, body: item.build() ); }).toList() ), ), ), ); } }