mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
623 lines
20 KiB
Dart
623 lines
20 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 'package:flutter/material.dart';
|
|
import 'package:flutter/scheduler.dart' show timeDilation;
|
|
|
|
final Map<int, Color> m2SwatchColors = <int, Color>{
|
|
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(const MyApp());
|
|
|
|
class MyApp extends StatelessWidget {
|
|
const MyApp({super.key});
|
|
|
|
static const String _title = 'Density Test';
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const MaterialApp(title: _title, home: MyHomePage());
|
|
}
|
|
}
|
|
|
|
class MyHomePage extends StatefulWidget {
|
|
const MyHomePage({super.key});
|
|
|
|
@override
|
|
State<MyHomePage> 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 = VisualDensity.standard;
|
|
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;
|
|
|
|
void reset() {
|
|
final 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({super.key, required this.label, this.onChanged, this.value});
|
|
|
|
final String label;
|
|
final ValueChanged<bool?>? onChanged;
|
|
final bool? value;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
Checkbox(onChanged: onChanged, value: value),
|
|
Text(label),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class Options extends StatefulWidget {
|
|
const Options(this.model, {super.key});
|
|
|
|
final OptionModel model;
|
|
|
|
@override
|
|
State<Options> createState() => _OptionsState();
|
|
}
|
|
|
|
class _OptionsState extends State<Options> {
|
|
@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) {
|
|
return switch (density) {
|
|
VisualDensity.standard => 'standard',
|
|
VisualDensity.compact => 'compact',
|
|
VisualDensity.comfortable => 'comfortable',
|
|
_ => 'custom',
|
|
};
|
|
}
|
|
|
|
VisualDensity _profileToDensity(String? profile) {
|
|
return switch (profile) {
|
|
'standard' => VisualDensity.standard,
|
|
'comfortable' => VisualDensity.comfortable,
|
|
'compact' => VisualDensity.compact,
|
|
'custom' || _ => 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: <Widget>[
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Row(
|
|
children: <Widget>[
|
|
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: <Widget>[
|
|
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: <Widget>[
|
|
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: <Widget>[
|
|
Theme(
|
|
data: Theme.of(context).copyWith(canvasColor: Colors.grey[600]),
|
|
child: DropdownButton<String>(
|
|
style: TextStyle(color: Colors.grey[50]),
|
|
isDense: true,
|
|
onChanged: (String? value) {
|
|
widget.model.density = _profileToDensity(value);
|
|
},
|
|
items: const <DropdownMenuItem<String>>[
|
|
DropdownMenuItem<String>(value: 'standard', child: Text('Standard')),
|
|
DropdownMenuItem<String>(
|
|
value: 'comfortable',
|
|
child: Text('Comfortable'),
|
|
),
|
|
DropdownMenuItem<String>(value: 'compact', child: Text('Compact')),
|
|
DropdownMenuItem<String>(value: 'custom', child: Text('Custom')),
|
|
],
|
|
value: _densityToProfile(widget.model.density),
|
|
),
|
|
),
|
|
LabeledCheckbox(
|
|
label: 'Enabled',
|
|
onChanged: (bool? checked) {
|
|
widget.model.enable = checked ?? false;
|
|
},
|
|
value: widget.model.enable,
|
|
),
|
|
LabeledCheckbox(
|
|
label: 'Slow',
|
|
onChanged: (bool? checked) {
|
|
widget.model.slowAnimations = checked ?? false;
|
|
Future<void>.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 ?? false;
|
|
},
|
|
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({required this.label, required this.child});
|
|
|
|
final String label;
|
|
final Widget child;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Column(
|
|
children: <Widget>[
|
|
Align(
|
|
alignment: AlignmentDirectional.topStart,
|
|
child: Text(label, textAlign: TextAlign.start),
|
|
),
|
|
child,
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MyHomePageState extends State<MyHomePage> {
|
|
static final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
|
final OptionModel _model = OptionModel();
|
|
final TextEditingController textController = TextEditingController();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_model.addListener(_modelChanged);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
_model.removeListener(_modelChanged);
|
|
}
|
|
|
|
void _modelChanged() {
|
|
setState(() {});
|
|
}
|
|
|
|
double sliderValue = 0.0;
|
|
List<bool> checkboxValues = <bool>[false, false, false, false];
|
|
List<IconData> iconValues = <IconData>[Icons.arrow_back, Icons.play_arrow, Icons.arrow_forward];
|
|
List<String> chipValues = <String>['Potato', 'Computer'];
|
|
int radioValue = 0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final 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 tiles = <Widget>[
|
|
_ControlTile(
|
|
label: _model.rtl ? 'حقل النص' : 'List Tile',
|
|
child: SizedBox(
|
|
width: 400,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
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: <Widget>[
|
|
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<Widget>.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(
|
|
foregroundColor: 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<Widget>.generate(checkboxValues.length, (int index) {
|
|
return Checkbox(
|
|
onChanged: _model.enable
|
|
? (bool? value) {
|
|
setState(() {
|
|
checkboxValues[index] = value ?? false;
|
|
});
|
|
}
|
|
: null,
|
|
value: checkboxValues[index],
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
_ControlTile(
|
|
label: _model.rtl ? 'زر الراديو' : 'Radio Button',
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: List<Widget>.generate(4, (int index) {
|
|
return Radio<int>(
|
|
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<Widget>.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: Builder(
|
|
builder: (BuildContext context) {
|
|
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
|
return MediaQuery(
|
|
data: mediaQueryData.copyWith(textScaler: TextScaler.linear(_model.size)),
|
|
child: SizedBox.expand(child: ListView(children: tiles)),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|