ML-137 Dialogs improvements (#138)

* Force dialogs to have the same width

* Fix `DialogPicker` bouncing when the first selected item is near the end
This commit is contained in:
Vadim 2023-11-14 12:26:34 +01:00 committed by GitHub
parent 6566108994
commit 19fc039723
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 139 additions and 128 deletions

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/res/dimens.dart';
@ -34,18 +35,20 @@ class _DialogFilterState<T> extends State<DialogFilter<T>> {
bool get _hasAnySelected => checkboxValues.contains(true); bool get _hasAnySelected => checkboxValues.contains(true);
bool get _hasAnyUnselected => checkboxValues.contains(false); bool get _hasAnyUnselected => checkboxValues.contains(false);
late final ScrollController _scrollController; final ScrollController _scrollController = ScrollController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
int i = 0; SchedulerBinding.instance.addPostFrameCallback((_) {
for (; i < checkboxValues.length; i++) { int i = 0;
if (checkboxValues[i]) { for (; i < checkboxValues.length; i++) {
break; if (checkboxValues[i]) {
break;
}
} }
} _scrollController.jumpTo((Dimens.grid56 * i).clamp(0, _scrollController.position.maxScrollExtent));
_scrollController = ScrollController(initialScrollOffset: Dimens.grid56 * i); });
} }
@override @override
@ -61,79 +64,80 @@ class _DialogFilterState<T> extends State<DialogFilter<T>> {
titlePadding: Dimens.dialogIconTitlePadding, titlePadding: Dimens.dialogIconTitlePadding,
title: Text(widget.title), title: Text(widget.title),
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: Column( content: SizedBox(
children: [ width: double.maxFinite,
Padding( child: Column(
padding: Dimens.dialogIconTitlePadding, children: [
child: Text(widget.description), Padding(
), padding: Dimens.dialogIconTitlePadding,
const Divider(), child: Text(widget.description),
Expanded( ),
child: SingleChildScrollView( const Divider(),
controller: _scrollController, Expanded(
child: Column( child: SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.stretch, controller: _scrollController,
mainAxisSize: MainAxisSize.min, child: Column(
children: List.generate( crossAxisAlignment: CrossAxisAlignment.stretch,
widget.values.length, mainAxisSize: MainAxisSize.min,
(index) => CheckboxListTile( children: List.generate(
value: checkboxValues[index], widget.values.length,
controlAffinity: ListTileControlAffinity.leading, (index) => CheckboxListTile(
title: Text( value: checkboxValues[index],
widget.titleAdapter(context, widget.values[index]), controlAffinity: ListTileControlAffinity.leading,
style: Theme.of(context).textTheme.bodyLarge, title: Text(
widget.titleAdapter(context, widget.values[index]),
style: Theme.of(context).textTheme.bodyLarge,
),
onChanged: (value) {
if (value != null) {
setState(() {
checkboxValues[index] = value;
});
}
},
), ),
onChanged: (value) {
if (value != null) {
setState(() {
checkboxValues[index] = value;
});
}
},
), ),
), ),
), ),
), ),
), const Divider(),
const Divider(), Padding(
Padding( padding: Dimens.dialogActionsPadding,
padding: Dimens.dialogActionsPadding, child: Row(
child: Row( children: [
children: [ SizedBox(
SizedBox( width: 40,
width: 40, child: IconButton(
child: IconButton( padding: EdgeInsets.zero,
padding: EdgeInsets.zero, icon: Icon(_hasAnyUnselected ? Icons.select_all : Icons.deselect),
icon: Icon(_hasAnyUnselected ? Icons.select_all : Icons.deselect), onPressed: _toggleAll,
onPressed: _toggleAll, tooltip: _hasAnyUnselected ? S.of(context).tooltipSelectAll : S.of(context).tooltipDesecelectAll,
tooltip: _hasAnyUnselected ),
? S.of(context).tooltipSelectAll
: S.of(context).tooltipDesecelectAll,
), ),
), const Spacer(),
const Spacer(), TextButton(
TextButton( onPressed: Navigator.of(context).pop,
onPressed: Navigator.of(context).pop, child: Text(S.of(context).cancel),
child: Text(S.of(context).cancel), ),
), TextButton(
TextButton( onPressed: _hasAnySelected
onPressed: _hasAnySelected ? () {
? () { final List<T> selectedValues = [];
final List<T> selectedValues = []; for (int i = 0; i < widget.values.length; i++) {
for (int i = 0; i < widget.values.length; i++) { if (checkboxValues[i]) {
if (checkboxValues[i]) { selectedValues.add(widget.values[i]);
selectedValues.add(widget.values[i]); }
} }
Navigator.of(context).pop(selectedValues);
} }
Navigator.of(context).pop(selectedValues); : null,
} child: Text(S.of(context).save),
: null, ),
child: Text(S.of(context).save), ],
), ),
], )
), ],
) ),
],
), ),
); );
} }

View file

@ -32,24 +32,28 @@ class _DialogPickerState<T> extends State<DialogPicker<T>> {
titlePadding: Dimens.dialogIconTitlePadding, titlePadding: Dimens.dialogIconTitlePadding,
title: Text(widget.title), title: Text(widget.title),
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: Column( content: SizedBox(
mainAxisSize: MainAxisSize.min, width: double.maxFinite,
children: widget.values child: Column(
.map( crossAxisAlignment: CrossAxisAlignment.stretch,
(e) => RadioListTile( mainAxisSize: MainAxisSize.min,
value: e, children: widget.values
groupValue: _selected, .map(
title: Text(widget.titleAdapter(context, e)), (e) => RadioListTile(
onChanged: (T? value) { value: e,
if (value != null) { groupValue: _selected,
setState(() { title: Text(widget.titleAdapter(context, e)),
_selected = value; onChanged: (T? value) {
}); if (value != null) {
} setState(() {
}, _selected = value;
), });
) }
.toList(), },
),
)
.toList(),
),
), ),
actionsPadding: Dimens.dialogActionsPadding, actionsPadding: Dimens.dialogActionsPadding,
actions: [ actions: [

View file

@ -36,47 +36,50 @@ class _DialogRangePickerState<T extends PhotographyValue> extends State<DialogRa
titlePadding: Dimens.dialogIconTitlePadding, titlePadding: Dimens.dialogIconTitlePadding,
title: Text(widget.title), title: Text(widget.title),
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: Column( content: SizedBox(
mainAxisSize: MainAxisSize.min, width: double.maxFinite,
children: [ child: Column(
Padding( mainAxisSize: MainAxisSize.min,
padding: Dimens.dialogIconTitlePadding, children: [
child: Text(widget.description), Padding(
), padding: Dimens.dialogIconTitlePadding,
Padding( child: Text(widget.description),
padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL),
child: DefaultTextStyle(
style: Theme.of(context).textTheme.bodyLarge!,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(widget.values[_start].toString()),
Text(widget.values[_end].toString()),
],
),
), ),
), Padding(
Row( padding: const EdgeInsets.symmetric(horizontal: Dimens.paddingL),
children: [ child: DefaultTextStyle(
Expanded( style: Theme.of(context).textTheme.bodyLarge!,
child: RangeSlider( child: Row(
values: RangeValues( mainAxisAlignment: MainAxisAlignment.spaceBetween,
_start.toDouble(), children: [
_end.toDouble(), Text(widget.values[_start].toString()),
), Text(widget.values[_end].toString()),
max: widget.values.length.toDouble() - 1, ],
divisions: widget.values.length - 1,
onChanged: (value) {
setState(() {
_start = value.start.round();
_end = value.end.round();
});
},
), ),
), ),
], ),
), Row(
], children: [
Expanded(
child: RangeSlider(
values: RangeValues(
_start.toDouble(),
_end.toDouble(),
),
max: widget.values.length.toDouble() - 1,
divisions: widget.values.length - 1,
onChanged: (value) {
setState(() {
_start = value.start.round();
_end = value.end.round();
});
},
),
),
],
),
],
),
), ),
actionsPadding: Dimens.dialogActionsPadding, actionsPadding: Dimens.dialogActionsPadding,
actions: [ actions: [