// 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 'package:flutter/foundation.dart'; import 'package:flutter/scheduler.dart' show timeDilation; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; final Map m2SwatchColors = { 50: const Color(0xfff2e7fe), 100: const Color(0xffd7b7fd), 200: const Color(0xffbb86fc), 300: const Color(0xff9e55fc), 400: const Color(0xff7f22fd), 500: const Color(0xff6200ee), 600: const Color(0xff4b00d1), 700: const Color(0xff3700b3), 800: const Color(0xff270096), 900: const Color(0xff270096), }; final MaterialColor m2Swatch = MaterialColor(m2SwatchColors[500].value, m2SwatchColors); void main() => runApp(MyApp()); class MyApp extends StatelessWidget { static const String _title = 'Density Test'; @override Widget build(BuildContext context) { return const MaterialApp( title: _title, home: MyHomePage(title: _title), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class OptionModel extends ChangeNotifier { double get size => _size; double _size = 1.0; set size(double size) { if (size != _size) { _size = size; notifyListeners(); } } VisualDensity get density => _density; VisualDensity _density = const VisualDensity(); set density(VisualDensity density) { if (density != _density) { _density = density; notifyListeners(); } } bool get enable => _enable; bool _enable = true; set enable(bool enable) { if (enable != _enable) { _enable = enable; notifyListeners(); } } bool get slowAnimations => _slowAnimations; bool _slowAnimations = false; set slowAnimations(bool slowAnimations) { if (slowAnimations != _slowAnimations) { _slowAnimations = slowAnimations; notifyListeners(); } } bool get rtl => _rtl; bool _rtl = false; set rtl(bool rtl) { if (rtl != _rtl) { _rtl = rtl; notifyListeners(); } } bool get longText => _longText; bool _longText = false; set longText(bool longText) { if (longText != _longText) { _longText = longText; notifyListeners(); } } void reset() { final OptionModel defaultModel = OptionModel(); _size = defaultModel.size; _enable = defaultModel.enable; _slowAnimations = defaultModel.slowAnimations; _longText = defaultModel.longText; _density = defaultModel.density; _rtl = defaultModel.rtl; notifyListeners(); } } class LabeledCheckbox extends StatelessWidget { const LabeledCheckbox({Key key, this.label, this.onChanged, this.value}) : super(key: key); final String label; final ValueChanged onChanged; final bool value; @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ Checkbox( onChanged: onChanged, value: value, ), Text(label), ], ); } } class Options extends StatefulWidget { const Options(this.model); final OptionModel model; @override _OptionsState createState() => _OptionsState(); } class _OptionsState extends State { @override void initState() { super.initState(); widget.model.addListener(_modelChanged); } @override void didUpdateWidget(Options oldWidget) { super.didUpdateWidget(oldWidget); if (widget.model != oldWidget.model) { oldWidget.model.removeListener(_modelChanged); widget.model.addListener(_modelChanged); } } @override void dispose() { super.dispose(); widget.model.removeListener(_modelChanged); } void _modelChanged() { setState(() {}); } double sliderValue = 0.0; String _densityToProfile(VisualDensity density) { if (density == VisualDensity.standard) { return 'standard'; } else if (density == VisualDensity.compact) { return 'compact'; } else if (density == VisualDensity.comfortable) { return 'comfortable'; } return 'custom'; } VisualDensity _profileToDensity(String profile) { switch (profile) { case 'standard': return VisualDensity.standard; case 'comfortable': return VisualDensity.comfortable; case 'compact': return VisualDensity.compact; case 'custom': default: return widget.model.density; } } @override Widget build(BuildContext context) { final SliderThemeData controlTheme = SliderTheme.of(context).copyWith( thumbColor: Colors.grey[50], activeTickMarkColor: Colors.deepPurple[200], activeTrackColor: Colors.deepPurple[300], inactiveTrackColor: Colors.grey[50], ); return Padding( padding: const EdgeInsets.fromLTRB(5.0, 0.0, 5.0, 10.0), child: Builder(builder: (BuildContext context) { return DefaultTextStyle( style: TextStyle(color: Colors.grey[50]), child: Column( children: [ Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ const Text('Text Scale'), Expanded( child: SliderTheme( data: controlTheme, child: Slider( label: '${widget.model.size}', min: 0.5, max: 3.0, onChanged: (double value) { widget.model.size = value; }, value: widget.model.size, ), ), ), Text( widget.model.size.toStringAsFixed(3), style: TextStyle(color: Colors.grey[50]), ), ], ), ), Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ const Text('X Density'), Expanded( child: SliderTheme( data: controlTheme, child: Slider( label: widget.model.density.horizontal.toStringAsFixed(1), min: VisualDensity.minimumDensity, max: VisualDensity.maximumDensity, onChanged: (double value) { widget.model.density = widget.model.density.copyWith( horizontal: value, vertical: widget.model.density.vertical, ); }, value: widget.model.density.horizontal, ), ), ), Text( widget.model.density.horizontal.toStringAsFixed(3), style: TextStyle(color: Colors.grey[50]), ), ], ), ), Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ const Text('Y Density'), Expanded( child: SliderTheme( data: controlTheme, child: Slider( label: widget.model.density.vertical.toStringAsFixed(1), min: VisualDensity.minimumDensity, max: VisualDensity.maximumDensity, onChanged: (double value) { widget.model.density = widget.model.density.copyWith( horizontal: widget.model.density.horizontal, vertical: value, ); }, value: widget.model.density.vertical, ), ), ), Text( widget.model.density.vertical.toStringAsFixed(3), style: TextStyle(color: Colors.grey[50]), ), ], ), ), Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ Theme( data: Theme.of(context).copyWith(canvasColor: Colors.grey[600]), child: DropdownButton( style: TextStyle(color: Colors.grey[50]), isDense: true, onChanged: (String value) { widget.model.density = _profileToDensity(value); }, items: const >[ DropdownMenuItem( child: Text('Standard'), value: 'standard', ), DropdownMenuItem(child: Text('Comfortable'), value: 'comfortable'), DropdownMenuItem(child: Text('Compact'), value: 'compact'), DropdownMenuItem(child: Text('Custom'), value: 'custom'), ], value: _densityToProfile(widget.model.density), ), ), LabeledCheckbox( label: 'Enabled', onChanged: (bool checked) { widget.model.enable = checked; }, value: widget.model.enable, ), LabeledCheckbox( label: 'Slow', onChanged: (bool checked) { widget.model.slowAnimations = checked; Future.delayed(const Duration(milliseconds: 150)).then((_) { if (widget.model.slowAnimations) { timeDilation = 20.0; } else { timeDilation = 1.0; } }); }, value: widget.model.slowAnimations, ), LabeledCheckbox( label: 'RTL', onChanged: (bool checked) { widget.model.rtl = checked; }, value: widget.model.rtl, ), MaterialButton( onPressed: () { widget.model.reset(); sliderValue = 0.0; }, child: Text('Reset', style: TextStyle(color: Colors.grey[50])), ), ], ), ], ), ); }), ); } } class _ControlTile extends StatelessWidget { const _ControlTile({Key key, @required this.label, @required this.child}) : assert(label != null), assert(child != null), super(key: key); final String label; final Widget child; @override Widget build(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ Align( alignment: AlignmentDirectional.topStart, child: Text( label, textAlign: TextAlign.start, ), ), child, ], ), ), ); } } class _MyHomePageState extends State { static final GlobalKey scaffoldKey = GlobalKey(); final OptionModel _model = OptionModel(); TextEditingController textController; @override void initState() { super.initState(); _model.addListener(_modelChanged); textController = TextEditingController(); } @override void dispose() { super.dispose(); _model.removeListener(_modelChanged); } void _modelChanged() { setState(() {}); } double sliderValue = 0.0; List checkboxValues = [false, false, false, false]; List iconValues = [Icons.arrow_back, Icons.play_arrow, Icons.arrow_forward]; List chipValues = ['Potato', 'Computer']; int radioValue = 0; @override Widget build(BuildContext context) { final ThemeData theme = ThemeData( primarySwatch: m2Swatch, ); final Widget label = Text(_model.rtl ? 'اضغط علي' : 'Press Me'); textController.text = _model.rtl ? 'يعتمد القرار الجيد على المعرفة وليس على الأرقام.' : 'A good decision is based on knowledge and not on numbers.'; final List tiles = [ _ControlTile( label: _model.rtl ? 'حقل النص' : 'List Tile', child: SizedBox( width: 400, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ListTile( title: Text(_model.rtl ? 'هذا عنوان طويل نسبيا' : 'This is a relatively long title'), onTap: () {}, ), ListTile( title: Text(_model.rtl ? 'هذا عنوان قصير' : 'This is a short title'), subtitle: Text(_model.rtl ? 'هذا عنوان فرعي مناسب.' : 'This is an appropriate subtitle.'), trailing: const Icon(Icons.check_box), onTap: () {}, ), ListTile( title: Text(_model.rtl ? 'هذا عنوان قصير' : 'This is a short title'), subtitle: Text(_model.rtl ? 'هذا عنوان فرعي مناسب.' : 'This is an appropriate subtitle.'), leading: const Icon(Icons.check_box), dense: true, onTap: () {}, ), ListTile( title: Text(_model.rtl ? 'هذا عنوان قصير' : 'This is a short title'), subtitle: Text(_model.rtl ? 'هذا عنوان فرعي مناسب.' : 'This is an appropriate subtitle.'), dense: true, leading: const Icon(Icons.add_box), trailing: const Icon(Icons.check_box), onTap: () {}, ), ListTile( title: Text(_model.rtl ? 'هذا عنوان قصير' : 'This is a short title'), subtitle: Text(_model.rtl ? 'هذا عنوان فرعي مناسب.' : 'This is an appropriate subtitle.'), isThreeLine: true, leading: const Icon(Icons.add_box), trailing: const Icon(Icons.check_box), onTap: () {}, ), ], ), ), ), _ControlTile( label: _model.rtl ? 'حقل النص' : 'Text Field', child: SizedBox( width: 300, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextField( controller: textController, decoration: const InputDecoration( hintText: 'Hint', helperText: 'Helper', labelText: 'Label', border: OutlineInputBorder(), ), ), TextField( controller: textController, ), TextField( controller: textController, maxLines: 3, ), ], ), ), ), _ControlTile( label: _model.rtl ? 'رقائق' : 'Chips', child: Column( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(chipValues.length, (int index) { return InputChip( onPressed: _model.enable ? () {} : null, onDeleted: _model.enable ? () {} : null, label: Text(chipValues[index]), deleteIcon: const Icon(Icons.delete), avatar: const Icon(Icons.play_arrow), ); }), ), ), _ControlTile( label: _model.rtl ? 'زر المواد' : 'Material Button', child: MaterialButton( color: m2Swatch[200], onPressed: _model.enable ? () {} : null, child: label, ), ), _ControlTile( label: _model.rtl ? 'زر مسطح' : 'Text Button', child: TextButton( style: TextButton.styleFrom( primary: Colors.white, backgroundColor: m2Swatch[200] ), onPressed: _model.enable ? () {} : null, child: label, ), ), _ControlTile( label: _model.rtl ? 'أثارت زر' : 'Elevated Button', child: ElevatedButton( style: TextButton.styleFrom(backgroundColor: m2Swatch[200]), onPressed: _model.enable ? () {} : null, child: label, ), ), _ControlTile( label: _model.rtl ? 'زر المخطط التفصيلي' : 'Outlined Button', child: OutlinedButton( onPressed: _model.enable ? () {} : null, child: label, ), ), _ControlTile( label: _model.rtl ? 'خانات الاختيار' : 'Checkboxes', child: Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(checkboxValues.length, (int index) { return Checkbox( onChanged: _model.enable ? (bool value) { setState(() { checkboxValues[index] = value; }); } : null, value: checkboxValues[index], ); }), ), ), _ControlTile( label: _model.rtl ? 'زر الراديو' : 'Radio Button', child: Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(4, (int index) { return Radio( onChanged: _model.enable ? (int value) { setState(() { radioValue = value; }); } : null, groupValue: radioValue, value: index, ); }), ), ), _ControlTile( label: _model.rtl ? 'زر الأيقونة' : 'Icon Button', child: Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(iconValues.length, (int index) { return IconButton( onPressed: _model.enable ? () {} : null, icon: Icon(iconValues[index]), ); }), ), ), ]; return SafeArea( child: Theme( data: theme, child: Scaffold( key: scaffoldKey, appBar: AppBar( title: const Text('Density'), bottom: PreferredSize( preferredSize: const Size.fromHeight(220.0), child: Options(_model), ), backgroundColor: const Color(0xff323232), ), body: DefaultTextStyle( style: const TextStyle( color: Colors.black, fontSize: 14.0, fontFamily: 'Roboto', fontStyle: FontStyle.normal, ), child: Theme( data: Theme.of(context).copyWith(visualDensity: _model.density), child: Directionality( textDirection: _model.rtl ? TextDirection.rtl : TextDirection.ltr, child: Scrollbar( child: MediaQuery( data: MediaQuery.of(context).copyWith(textScaleFactor: _model.size), child: SizedBox.expand( child: ListView( children: tiles, ), ), ), ), ), ), ), ), ), ); } }