Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,13 @@ class CupertinoSlidingSegmentedButtonControl extends StatelessWidget {
debugPrint("CupertinoSlidingSegmentedButtonControl build: ${control.id}");

var controls = control.buildWidgets("controls");

if (controls.length < 2) {
return const ErrorControl(
"CupertinoSlidingSegmentedButton must have at minimum two visible controls");
}

var button = CupertinoSlidingSegmentedControl(
groupValue: control.getInt("selected_index"),
groupValue: control.getInt("selected_index", 0)!,
proportionalWidth: control.getBool("proportional_width", false)!,
backgroundColor: control.getColor(
"bgcolor", context, CupertinoColors.tertiarySystemFill)!,
Expand All @@ -36,15 +35,13 @@ class CupertinoSlidingSegmentedButtonControl extends StatelessWidget {
"thumb_color",
context,
const CupertinoDynamicColor.withBrightness(
color: Color(0xFFFFFFFF),
darkColor: Color(0xFF636366),
))!,
color: Color(0xFFFFFFFF), darkColor: Color(0xFF636366)))!,
children: controls.asMap().map((i, c) => MapEntry(i, c)),
onValueChanged: (int? index) {
if (!control.disabled) {
control
.updateProperties({"selected_index": index ?? 0}, notify: true);
control.triggerEvent("change", index ?? 0);
index = index ?? 0;
control.updateProperties({"selected_index": index}, notify: true);
control.triggerEvent("change", index);
}
},
);
Expand Down
155 changes: 87 additions & 68 deletions packages/flet/lib/src/controls/expansion_tile.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'package:flet/src/utils/animations.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

import '../extensions/control.dart';
import '../models/control.dart';
Expand All @@ -11,97 +13,114 @@ import '../utils/numbers.dart';
import '../utils/theme.dart';
import '../widgets/error.dart';
import 'base_controls.dart';
import 'control_widget.dart';

class ExpansionTileControl extends StatelessWidget {
class ExpansionTileControl extends StatefulWidget {
final Control control;

const ExpansionTileControl({
super.key,
required this.control,
});
const ExpansionTileControl({super.key, required this.control});

@override
Widget build(BuildContext context) {
debugPrint("ExpansionTile build: ${control.id}");
State<ExpansionTileControl> createState() => _ExpansionTileControlState();
}

class _ExpansionTileControlState extends State<ExpansionTileControl> {
late final ExpansibleController _controller;
bool _expanded = false;

@override
void initState() {
super.initState();
_controller = ExpansibleController();
_expanded = widget.control.getBool("expanded", false)!;
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

var controls = control
.children("controls")
.map((child) => ControlWidget(control: child, key: ValueKey(child.id)))
.toList();
// Schedules an update to the controller after the current frame.
// This ensures the expansion/collapse animation is triggered safely.
void _scheduleControllerUpdate(bool expanded) {
SchedulerBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; // Prevents updates if the widget is disposed.

var leading = control.buildIconOrWidget("leading");
var title = control.buildTextOrWidget("title");
var subtitle = control.buildTextOrWidget("subtitle");
var trailing = control.buildIconOrWidget("trailing");
if (expanded) {
_controller.expand();
} else {
_controller.collapse();
}
});
}

@override
Widget build(BuildContext context) {
debugPrint("ExpansionTile build: ${widget.control.id}");

final title = widget.control.buildTextOrWidget("title");
if (title == null) {
return const ErrorControl(
"ExpansionTile.title must be provided and visible");
}

bool maintainState = control.getBool("maintain_state", false)!;
bool initiallyExpanded = control.getBool("initially_expanded", false)!;

var iconColor = control.getColor("icon_color", context);
var textColor = control.getColor("text_color", context);
var bgColor = control.getColor("bgcolor", context);
var collapsedBgColor = control.getColor("collapsed_bgcolor", context);
var collapsedIconColor = control.getColor("collapsed_icon_color", context);
var collapsedTextColor = control.getColor("collapsed_text_color", context);

var affinity = control.getListTileControlAffinity(
"affinity", ListTileControlAffinity.platform)!;
var clipBehavior = parseClip(control.getString("clip_behavior"));
var expanded = widget.control.getBool("expanded", false)!;
if (_expanded != expanded) {
_expanded = expanded;
_scheduleControllerUpdate(expanded);
}

var expandedCrossAxisAlignment = control.getCrossAxisAlignment(
var expandedCrossAxisAlignment = widget.control.getCrossAxisAlignment(
"expanded_cross_axis_alignment", CrossAxisAlignment.center)!;

if (expandedCrossAxisAlignment == CrossAxisAlignment.baseline) {
return const ErrorControl(
'CrossAxisAlignment.baseline is not supported since the expanded '
'CrossAxisAlignment.BASELINE is not supported since the expanded '
'controls are aligned in a column, not a row. '
'Try aligning the controls differently.');
}

Function(bool)? onChange = !control.disabled
? (expanded) {
control.triggerEvent("change", expanded);
}
: null;

Widget tile = ExpansionTile(
controlAffinity: affinity,
childrenPadding: control.getPadding("controls_padding"),
tilePadding: control.getEdgeInsets("tile_padding"),
expandedAlignment: control.getAlignment("expanded_alignment"),
expandedCrossAxisAlignment:
control.getCrossAxisAlignment("expanded_cross_axis_alignment"),
backgroundColor: bgColor,
iconColor: iconColor,
textColor: textColor,
collapsedBackgroundColor: collapsedBgColor,
collapsedIconColor: collapsedIconColor,
collapsedTextColor: collapsedTextColor,
maintainState: maintainState,
initiallyExpanded: initiallyExpanded,
clipBehavior: clipBehavior,
shape: control.getShape("shape", Theme.of(context)),
collapsedShape: control.getShape("collapsed_shape", Theme.of(context)),
onExpansionChanged: onChange,
visualDensity: control.getVisualDensity("visual_density"),
enableFeedback: control.getBool("enable_feedback"),
showTrailingIcon: control.getBool("show_trailing_icon", true)!,
enabled: !control.disabled,
minTileHeight: control.getDouble("min_tile_height"),
dense: control.getBool("dense"),
leading: leading,
final tile = ExpansionTile(
controller: _controller,
controlAffinity: widget.control.getListTileControlAffinity("affinity"),
childrenPadding: widget.control.getPadding("controls_padding"),
tilePadding: widget.control.getEdgeInsets("tile_padding"),
expandedAlignment: widget.control.getAlignment("expanded_alignment"),
expandedCrossAxisAlignment: expandedCrossAxisAlignment,
backgroundColor: widget.control.getColor("bgcolor", context),
iconColor: widget.control.getColor("icon_color", context),
textColor: widget.control.getColor("text_color", context),
collapsedBackgroundColor:
widget.control.getColor("collapsed_bgcolor", context),
collapsedIconColor:
widget.control.getColor("collapsed_icon_color", context),
collapsedTextColor:
widget.control.getColor("collapsed_text_color", context),
maintainState: widget.control.getBool("maintain_state", false)!,
initiallyExpanded: expanded,
clipBehavior: widget.control.getClipBehavior("clip_behavior"),
shape: widget.control.getShape("shape", Theme.of(context)),
collapsedShape:
widget.control.getShape("collapsed_shape", Theme.of(context)),
onExpansionChanged: (bool expanded) {
_expanded = expanded;
widget.control.updateProperties({"expanded": expanded});
widget.control.triggerEvent("change", expanded);
},
visualDensity: widget.control.getVisualDensity("visual_density"),
enableFeedback: widget.control.getBool("enable_feedback"),
showTrailingIcon: widget.control.getBool("show_trailing_icon", true)!,
enabled: !widget.control.disabled,
minTileHeight: widget.control.getDouble("min_tile_height"),
dense: widget.control.getBool("dense"),
expansionAnimationStyle:
widget.control.getAnimationStyle("animation_style"),
leading: widget.control.buildIconOrWidget("leading"),
title: title,
subtitle: subtitle,
trailing: trailing,
children: controls,
subtitle: widget.control.buildTextOrWidget("subtitle"),
trailing: widget.control.buildIconOrWidget("trailing"),
children: widget.control.buildWidgets("controls"),
);

return LayoutControl(control: control, child: tile);
return LayoutControl(control: widget.control, child: tile);
}
}
8 changes: 8 additions & 0 deletions packages/flet/lib/src/utils/theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '../flet_backend.dart';
import '../models/control.dart';
import '../utils/transforms.dart';
import 'alignment.dart';
import 'animations.dart';
import 'borders.dart';
import 'box.dart';
import 'buttons.dart';
Expand Down Expand Up @@ -850,6 +851,9 @@ ListTileThemeData? parseListTileTheme(
parseTextStyle(value["leading_and_trailing_text_style"], theme),
mouseCursor: parseWidgetStateMouseCursor(value["mouse_cursor"]),
minTileHeight: parseDouble(value["min_height"]),
controlAffinity: parseListTileControlAffinity(value["affinity"]),
style: parseListTileStyle(value["style"]),
titleAlignment: parseListTileTitleAlignment(value["title_alignment"]),
);
}

Expand Down Expand Up @@ -894,6 +898,10 @@ ExpansionTileThemeData? parseExpansionTileTheme(
tilePadding: parsePadding(value["tile_padding"]),
expandedAlignment: parseAlignment(value["expanded_alignment"]),
childrenPadding: parsePadding(value["controls_padding"]),
shape: parseShape(value["shape"], theme),
collapsedShape: parseShape(value["collapsed_shape"], theme),
expansionAnimationStyle:
parseAnimationStyle(value["expansion_animation_style"]),
);
}

Expand Down
9 changes: 4 additions & 5 deletions sdk/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ Package relationships:

```mermaid
graph TD;
flet-core-->flet-runtime;
flet-core-->flet-pyodide;
flet-runtime-->flet-embed;
flet-runtime-->flet;
```
flet --> flet-cli;
flet --> flet-desktop;
flet --> flet-web;
```
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def example():
title=ft.Text("ExpansionTile 3"),
subtitle=ft.Text("Leading expansion arrow icon"),
affinity=ft.TileAffinity.LEADING,
initially_expanded=True,
expanded=True,
collapsed_text_color=ft.Colors.BLUE,
text_color=ft.Colors.BLUE,
controls=[
Expand Down
Empty file.
29 changes: 15 additions & 14 deletions sdk/python/examples/controls/expansion_tile/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def main(page: ft.Page):
page.spacing = 0
page.padding = 0

def handle_expansion_tile_change(e: ft.Event[ft.ExpansionTile]):
def handle_tile_change(e: ft.Event[ft.ExpansionTile]):
page.show_dialog(
ft.SnackBar(
duration=1000,
Expand All @@ -25,44 +25,45 @@ def handle_expansion_tile_change(e: ft.Event[ft.ExpansionTile]):

page.add(
ft.ExpansionTile(
expanded=True,
title=ft.Text("ExpansionTile 1"),
subtitle=ft.Text("Trailing expansion arrow icon"),
bgcolor=ft.Colors.BLUE_GREY_200,
collapsed_bgcolor=ft.Colors.BLUE_GREY_200,
affinity=ft.TileAffinity.PLATFORM,
maintain_state=True,
collapsed_text_color=ft.Colors.RED,
text_color=ft.Colors.RED,
controls=[
ft.ListTile(
title=ft.Text("This is sub-tile number 1"),
bgcolor=ft.Colors.BLUE_GREY_200,
)
ft.ListTile(title=ft.Text("This is sub-tile number 1.1")),
ft.ListTile(title=ft.Text("This is sub-tile number 1.2")),
],
),
ft.ExpansionTile(
expanded=True,
title=ft.Text("ExpansionTile 2"),
subtitle=ft.Text("Custom expansion arrow icon"),
trailing=ft.Icon(ft.Icons.ARROW_DROP_DOWN),
collapsed_text_color=ft.Colors.GREEN,
text_color=ft.Colors.GREEN,
on_change=handle_expansion_tile_change,
controls=[ft.ListTile(title=ft.Text("This is sub-tile number 2"))],
on_change=handle_tile_change,
controls=[
ft.ListTile(title=ft.Text("This is sub-tile number 2.1")),
ft.ListTile(title=ft.Text("This is sub-tile number 2.2")),
],
),
ft.ExpansionTile(
expanded=True,
title=ft.Text("ExpansionTile 3"),
subtitle=ft.Text("Leading expansion arrow icon"),
affinity=ft.TileAffinity.LEADING,
initially_expanded=True,
collapsed_text_color=ft.Colors.BLUE_800,
text_color=ft.Colors.BLUE_200,
controls=[
ft.ListTile(title=ft.Text("This is sub-tile number 3")),
ft.ListTile(title=ft.Text("This is sub-tile number 4")),
ft.ListTile(title=ft.Text("This is sub-tile number 5")),
ft.ListTile(title=ft.Text("This is sub-tile number 3.1")),
ft.ListTile(title=ft.Text("This is sub-tile number 3.2")),
],
),
)


ft.run(main)
if __name__ == "__main__":
ft.run(main)
3 changes: 2 additions & 1 deletion sdk/python/examples/controls/expansion_tile/borders.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ def main(page: ft.Page):
)


ft.run(main)
if __name__ == "__main__":
ft.run(main)
45 changes: 45 additions & 0 deletions sdk/python/examples/controls/expansion_tile/custom_animations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import flet as ft


def main(page: ft.Page):
page.horizontal_alignment = ft.CrossAxisAlignment.CENTER
page.spacing = 20

def switch_animation(e: ft.Event[ft.CupertinoSlidingSegmentedButton]):
if e.control.selected_index == 0:
tile.animation_style = None
elif e.control.selected_index == 1:
tile.animation_style = ft.AnimationStyle(
curve=ft.AnimationCurve.BOUNCE_OUT,
duration=ft.Duration(seconds=5),
)
else:
tile.animation_style = ft.AnimationStyle.no_animation()

page.add(
ft.CupertinoSlidingSegmentedButton(
selected_index=0,
thumb_color=ft.Colors.BLUE_400,
on_change=switch_animation,
controls=[
ft.Text("Default animation"),
ft.Text("Custom animation"),
ft.Text("No animation"),
],
),
tile := ft.ExpansionTile(
expanded=True,
title=ft.Text(
"Expand/Collapse me while being attentive to the animations!"
),
controls=[
ft.ListTile(title=ft.Text("Sub-item 1")),
ft.ListTile(title=ft.Text("Sub-item 2")),
ft.ListTile(title=ft.Text("Sub-item 3")),
],
),
)


if __name__ == "__main__":
ft.run(main)
Binary file not shown.
Loading
Loading