1+ import 'package:flet/src/utils/animations.dart' ;
12import 'package:flutter/material.dart' ;
3+ import 'package:flutter/scheduler.dart' ;
24
35import '../extensions/control.dart' ;
46import '../models/control.dart' ;
@@ -11,97 +13,114 @@ import '../utils/numbers.dart';
1113import '../utils/theme.dart' ;
1214import '../widgets/error.dart' ;
1315import 'base_controls.dart' ;
14- import 'control_widget.dart' ;
1516
16- class ExpansionTileControl extends StatelessWidget {
17+ class ExpansionTileControl extends StatefulWidget {
1718 final Control control;
1819
19- const ExpansionTileControl ({
20- super .key,
21- required this .control,
22- });
20+ const ExpansionTileControl ({super .key, required this .control});
2321
2422 @override
25- Widget build (BuildContext context) {
26- debugPrint ("ExpansionTile build: ${control .id }" );
23+ State <ExpansionTileControl > createState () => _ExpansionTileControlState ();
24+ }
25+
26+ class _ExpansionTileControlState extends State <ExpansionTileControl > {
27+ late final ExpansibleController _controller;
28+ bool _expanded = false ;
29+
30+ @override
31+ void initState () {
32+ super .initState ();
33+ _controller = ExpansibleController ();
34+ _expanded = widget.control.getBool ("expanded" , false )! ;
35+ }
36+
37+ @override
38+ void dispose () {
39+ _controller.dispose ();
40+ super .dispose ();
41+ }
2742
28- var controls = control
29- .children ("controls" )
30- .map ((child) => ControlWidget (control: child, key: ValueKey (child.id)))
31- .toList ();
43+ // Schedules an update to the controller after the current frame.
44+ // This ensures the expansion/collapse animation is triggered safely.
45+ void _scheduleControllerUpdate (bool expanded) {
46+ SchedulerBinding .instance.addPostFrameCallback ((_) {
47+ if (! mounted) return ; // Prevents updates if the widget is disposed.
3248
33- var leading = control.buildIconOrWidget ("leading" );
34- var title = control.buildTextOrWidget ("title" );
35- var subtitle = control.buildTextOrWidget ("subtitle" );
36- var trailing = control.buildIconOrWidget ("trailing" );
49+ if (expanded) {
50+ _controller.expand ();
51+ } else {
52+ _controller.collapse ();
53+ }
54+ });
55+ }
3756
57+ @override
58+ Widget build (BuildContext context) {
59+ debugPrint ("ExpansionTile build: ${widget .control .id }" );
60+
61+ final title = widget.control.buildTextOrWidget ("title" );
3862 if (title == null ) {
3963 return const ErrorControl (
4064 "ExpansionTile.title must be provided and visible" );
4165 }
4266
43- bool maintainState = control.getBool ("maintain_state" , false )! ;
44- bool initiallyExpanded = control.getBool ("initially_expanded" , false )! ;
45-
46- var iconColor = control.getColor ("icon_color" , context);
47- var textColor = control.getColor ("text_color" , context);
48- var bgColor = control.getColor ("bgcolor" , context);
49- var collapsedBgColor = control.getColor ("collapsed_bgcolor" , context);
50- var collapsedIconColor = control.getColor ("collapsed_icon_color" , context);
51- var collapsedTextColor = control.getColor ("collapsed_text_color" , context);
52-
53- var affinity = control.getListTileControlAffinity (
54- "affinity" , ListTileControlAffinity .platform)! ;
55- var clipBehavior = parseClip (control.getString ("clip_behavior" ));
67+ var expanded = widget.control.getBool ("expanded" , false )! ;
68+ if (_expanded != expanded) {
69+ _expanded = expanded;
70+ _scheduleControllerUpdate (expanded);
71+ }
5672
57- var expandedCrossAxisAlignment = control.getCrossAxisAlignment (
73+ var expandedCrossAxisAlignment = widget. control.getCrossAxisAlignment (
5874 "expanded_cross_axis_alignment" , CrossAxisAlignment .center)! ;
59-
6075 if (expandedCrossAxisAlignment == CrossAxisAlignment .baseline) {
6176 return const ErrorControl (
62- 'CrossAxisAlignment.baseline is not supported since the expanded '
77+ 'CrossAxisAlignment.BASELINE is not supported since the expanded '
6378 'controls are aligned in a column, not a row. '
6479 'Try aligning the controls differently.' );
6580 }
6681
67- Function (bool )? onChange = ! control.disabled
68- ? (expanded) {
69- control.triggerEvent ("change" , expanded);
70- }
71- : null ;
72-
73- Widget tile = ExpansionTile (
74- controlAffinity: affinity,
75- childrenPadding: control.getPadding ("controls_padding" ),
76- tilePadding: control.getEdgeInsets ("tile_padding" ),
77- expandedAlignment: control.getAlignment ("expanded_alignment" ),
78- expandedCrossAxisAlignment:
79- control.getCrossAxisAlignment ("expanded_cross_axis_alignment" ),
80- backgroundColor: bgColor,
81- iconColor: iconColor,
82- textColor: textColor,
83- collapsedBackgroundColor: collapsedBgColor,
84- collapsedIconColor: collapsedIconColor,
85- collapsedTextColor: collapsedTextColor,
86- maintainState: maintainState,
87- initiallyExpanded: initiallyExpanded,
88- clipBehavior: clipBehavior,
89- shape: control.getShape ("shape" , Theme .of (context)),
90- collapsedShape: control.getShape ("collapsed_shape" , Theme .of (context)),
91- onExpansionChanged: onChange,
92- visualDensity: control.getVisualDensity ("visual_density" ),
93- enableFeedback: control.getBool ("enable_feedback" ),
94- showTrailingIcon: control.getBool ("show_trailing_icon" , true )! ,
95- enabled: ! control.disabled,
96- minTileHeight: control.getDouble ("min_tile_height" ),
97- dense: control.getBool ("dense" ),
98- leading: leading,
82+ final tile = ExpansionTile (
83+ controller: _controller,
84+ controlAffinity: widget.control.getListTileControlAffinity ("affinity" ),
85+ childrenPadding: widget.control.getPadding ("controls_padding" ),
86+ tilePadding: widget.control.getEdgeInsets ("tile_padding" ),
87+ expandedAlignment: widget.control.getAlignment ("expanded_alignment" ),
88+ expandedCrossAxisAlignment: expandedCrossAxisAlignment,
89+ backgroundColor: widget.control.getColor ("bgcolor" , context),
90+ iconColor: widget.control.getColor ("icon_color" , context),
91+ textColor: widget.control.getColor ("text_color" , context),
92+ collapsedBackgroundColor:
93+ widget.control.getColor ("collapsed_bgcolor" , context),
94+ collapsedIconColor:
95+ widget.control.getColor ("collapsed_icon_color" , context),
96+ collapsedTextColor:
97+ widget.control.getColor ("collapsed_text_color" , context),
98+ maintainState: widget.control.getBool ("maintain_state" , false )! ,
99+ initiallyExpanded: expanded,
100+ clipBehavior: widget.control.getClipBehavior ("clip_behavior" ),
101+ shape: widget.control.getShape ("shape" , Theme .of (context)),
102+ collapsedShape:
103+ widget.control.getShape ("collapsed_shape" , Theme .of (context)),
104+ onExpansionChanged: (bool expanded) {
105+ _expanded = expanded;
106+ widget.control.updateProperties ({"expanded" : expanded});
107+ widget.control.triggerEvent ("change" , expanded);
108+ },
109+ visualDensity: widget.control.getVisualDensity ("visual_density" ),
110+ enableFeedback: widget.control.getBool ("enable_feedback" ),
111+ showTrailingIcon: widget.control.getBool ("show_trailing_icon" , true )! ,
112+ enabled: ! widget.control.disabled,
113+ minTileHeight: widget.control.getDouble ("min_tile_height" ),
114+ dense: widget.control.getBool ("dense" ),
115+ expansionAnimationStyle:
116+ widget.control.getAnimationStyle ("animation_style" ),
117+ leading: widget.control.buildIconOrWidget ("leading" ),
99118 title: title,
100- subtitle: subtitle,
101- trailing: trailing,
102- children: controls,
119+ subtitle: widget.control. buildTextOrWidget ( " subtitle" ) ,
120+ trailing: widget.control. buildIconOrWidget ( " trailing" ) ,
121+ children: widget.control. buildWidgets ( " controls" ) ,
103122 );
104123
105- return LayoutControl (control: control, child: tile);
124+ return LayoutControl (control: widget. control, child: tile);
106125 }
107126}
0 commit comments