Skip to content

Commit a8cef8f

Browse files
v1: Refactor NavigationDrawer, Pagelet API and usage (#5754)
* Refactor Pagelet drawer handling and update examples Refactored Pagelet drawer and end drawer open/close logic to use async invoke methods instead of property updates. Updated Python examples to use new async drawer methods and added a declarative example. Cleaned up related Dart code and removed unused imports. * Clip scaffold when drawer is present in Pagelet Wraps the scaffold in a ClipRect when a drawer or endDrawer is present to ensure drawer animations remain hidden outside the pagelet bounds. * Refactor NavigationDrawer API and usage Removed NavigationDrawerPosition enum and dialog-based drawer handling. NavigationDrawer is now set via 'drawer' and 'end_drawer' properties on Page/Pagelet, with new async methods for showing and closing drawers. Updated Dart and Python code to support direct drawer control, removed ScaffoldKeyProvider, and revised examples and tests to use the new API. Fix #5386 * Remove NavigationDrawerPosition type documentation Deleted the navigationdrawerposition.md documentation file and its reference from mkdocs.yml, likely because the type is deprecated or no longer relevant. * Refactor navigation drawer tests to use resize_page Replaces manual window size assignments in navigation drawer integration tests with the new async resize_page method for consistency and reliability. Adds the resize_page utility to FletTestApp, improving test maintainability and accuracy. Updates golden images to reflect new window dimensions. * Improve window resize handling in FletTestApp Replaces event-based waiting with polling to ensure window dimensions are updated correctly before and after adjusting for window chrome size. This change improves reliability of window resizing during tests. * Simplify window size adjustment logic Refactored the method to set window size by removing redundant update and wait steps. The window size is now set directly with chrome adjustments, improving efficiency. * Refactor Pagelet usage in basic_declarative.py Simplifies Pagelet reference handling by removing the use of ft.Ref and cast, assigning the Pagelet instance directly to a variable. Updates drawer handler functions to use the new variable reference. * Add shape drawer example and update test resizing Added a new declarative shape drawer example app using Flet's canvas. Updated integration tests for Cupertino and Material controls to use the new `resize_page` method for setting window size, replacing direct window property assignments. Updated multiple golden image files for test cases. * Refactor window resizing in integration tests Replaces direct assignment of page window width and height with calls to the async resize_page method across multiple integration test files. This improves consistency and ensures proper asynchronous handling of window resizing for screenshot-based tests. Also updates related golden images to reflect any visual changes from the new resizing approach. * Improve screenshot similarity assertion message Enhanced the assertion error message in flet_test_app.py to include actual and threshold similarity values for easier debugging. Updated golden image for Cupertino bottom sheet basic test. * Refactor shader mask test and update golden image Reordered imports and function calls in test_shader_mask.py for clarity and consistency. Updated the golden image for shader mask on macOS. Minor changes in flet_test_app.py to support integration testing. * Rename Dropdown 'change' event to 'text_change' Updated the event name from 'change' to 'text_change' for dropdown controls in Dart and Python implementations. Adjusted related handler and usage in example code to improve clarity between selection changes and text input changes. Close #5760 --------- Co-authored-by: InesaFitsner <[email protected]>
1 parent 68b4344 commit a8cef8f

File tree

165 files changed

+571
-418
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

165 files changed

+571
-418
lines changed

packages/flet/lib/src/controls/dropdown.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class _DropdownControlState extends State<DropdownControl> {
3333
debugPrint("Typed text: ${_controller.text}");
3434
if (_controller.text != widget.control.getString("text")) {
3535
widget.control.updateProperties({"text": _controller.text});
36-
widget.control.triggerEvent("change", _controller.text);
36+
widget.control.triggerEvent("text_change", _controller.text);
3737
}
3838
}
3939

packages/flet/lib/src/controls/navigation_drawer.dart

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import '../utils/colors.dart';
66
import '../utils/edge_insets.dart';
77
import '../utils/icons.dart';
88
import '../utils/numbers.dart';
9-
import '../widgets/scaffold_key_provider.dart';
109
import 'base_controls.dart';
1110
import 'control_widget.dart';
1211

@@ -83,21 +82,6 @@ class _NavigationDrawerControlState extends State<NavigationDrawerControl> {
8382
}).toList(),
8483
);
8584

86-
WidgetsBinding.instance.addPostFrameCallback((_) {
87-
if (widget.control.getBool("open", false) == false) {
88-
if (endDrawer &&
89-
ScaffoldKeyProvider.of(context)?.currentState?.isEndDrawerOpen ==
90-
true) {
91-
ScaffoldKeyProvider.of(context)?.currentState?.closeEndDrawer();
92-
} else if (ScaffoldKeyProvider.of(context)
93-
?.currentState
94-
?.isDrawerOpen ==
95-
true) {
96-
ScaffoldKeyProvider.of(context)?.currentState?.closeDrawer();
97-
}
98-
}
99-
});
100-
10185
return BaseControl(control: widget.control, child: drawer);
10286
}
10387
}

packages/flet/lib/src/controls/pagelet.dart

Lines changed: 46 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'package:flet/src/utils/buttons.dart';
22
import 'package:flet/src/utils/colors.dart';
3-
import 'package:flet/src/utils/numbers.dart';
43
import 'package:flutter/cupertino.dart';
54
import 'package:flutter/material.dart';
65

@@ -11,8 +10,8 @@ import '../utils/platform.dart';
1110
import '../widgets/error.dart';
1211
import 'app_bar.dart';
1312
import 'base_controls.dart';
13+
import 'control_widget.dart';
1414
import 'cupertino_app_bar.dart';
15-
import 'navigation_drawer.dart';
1615

1716
class PageletControl extends StatefulWidget {
1817
final Control control;
@@ -27,6 +26,38 @@ class PageletControl extends StatefulWidget {
2726
class _PageletControlState extends State<PageletControl> {
2827
final scaffoldKey = GlobalKey<ScaffoldState>();
2928

29+
@override
30+
void initState() {
31+
super.initState();
32+
widget.control.addInvokeMethodListener(_invokeMethod);
33+
}
34+
35+
@override
36+
void dispose() {
37+
widget.control.removeInvokeMethodListener(_invokeMethod);
38+
super.dispose();
39+
}
40+
41+
Future<dynamic> _invokeMethod(String name, dynamic args) async {
42+
debugPrint("Pagelet.$name($args)");
43+
switch (name) {
44+
case "show_drawer":
45+
scaffoldKey.currentState?.openDrawer();
46+
break;
47+
case "close_drawer":
48+
scaffoldKey.currentState?.closeDrawer();
49+
break;
50+
case "show_end_drawer":
51+
scaffoldKey.currentState?.openEndDrawer();
52+
break;
53+
case "close_end_drawer":
54+
scaffoldKey.currentState?.closeEndDrawer();
55+
break;
56+
default:
57+
throw Exception("Unknown Pagelet method: $name");
58+
}
59+
}
60+
3061
@override
3162
Widget build(BuildContext context) {
3263
debugPrint("Pagelet build: ${widget.control.id}");
@@ -38,6 +69,7 @@ class _PageletControlState extends State<PageletControl> {
3869
var bottomSheet = widget.control.buildWidget("bottom_sheet");
3970
var drawer = widget.control.child("drawer");
4071
var endDrawer = widget.control.child("end_drawer");
72+
var hasDrawer = drawer != null || endDrawer != null;
4173
var fab = widget.control.buildWidget("floating_action_button");
4274

4375
if (content == null) {
@@ -50,66 +82,10 @@ class _PageletControlState extends State<PageletControl> {
5082

5183
var bnb = navigationBar ?? bottomAppBar;
5284

53-
final bool? drawerOpened = widget.control.getBool("drawer_opened");
54-
final bool? endDrawerOpened = widget.control.getBool("end_drawer_opened");
55-
final fabLocation = widget.control.getFloatingActionButtonLocation(
56-
"floating_action_button_location",
57-
FloatingActionButtonLocation.endFloat);
58-
59-
void dismissDrawer(dynamic id) {
60-
// fixme: id
61-
widget.control.updateProperties({"open": false});
62-
widget.control.triggerEvent("dismiss");
85+
void dismissDrawer(int id) {
86+
widget.control.backend.triggerControlEventById(id, "dismiss");
6387
}
6488

65-
WidgetsBinding.instance.addPostFrameCallback((_) {
66-
if (drawer != null) {
67-
if (scaffoldKey.currentState?.isDrawerOpen == false &&
68-
drawerOpened == true) {
69-
widget.control
70-
.updateProperties({"drawer_opened": false}, python: false);
71-
dismissDrawer(drawer.id);
72-
}
73-
if (drawer.getBool("open", false)! && drawerOpened != true) {
74-
if (scaffoldKey.currentState?.isEndDrawerOpen == true) {
75-
scaffoldKey.currentState?.closeEndDrawer();
76-
}
77-
Future.delayed(const Duration(milliseconds: 1)).then((value) {
78-
scaffoldKey.currentState?.openDrawer();
79-
widget.control
80-
.updateProperties({"drawer_opened": true}, python: false);
81-
});
82-
} else if (!drawer.getBool("open", false)! && drawerOpened == true) {
83-
scaffoldKey.currentState?.closeDrawer();
84-
widget.control
85-
.updateProperties({"drawer_opened": false}, python: false);
86-
}
87-
}
88-
if (endDrawer != null) {
89-
if (scaffoldKey.currentState?.isEndDrawerOpen == false &&
90-
endDrawerOpened == true) {
91-
widget.control
92-
.updateProperties({"end_drawer_opened": false}, python: false);
93-
dismissDrawer(endDrawer.id);
94-
}
95-
if (endDrawer.getBool("open", false)! && endDrawerOpened != true) {
96-
if (scaffoldKey.currentState?.isDrawerOpen == true) {
97-
scaffoldKey.currentState?.closeDrawer();
98-
}
99-
Future.delayed(const Duration(milliseconds: 1)).then((value) {
100-
scaffoldKey.currentState?.openEndDrawer();
101-
widget.control
102-
.updateProperties({"end_drawer_opened": true}, python: false);
103-
});
104-
} else if (!endDrawer.getBool("open", false)! &&
105-
endDrawerOpened == true) {
106-
scaffoldKey.currentState?.closeEndDrawer();
107-
widget.control
108-
.updateProperties({"end_drawer_opened": false}, python: false);
109-
}
110-
}
111-
});
112-
11389
var bar = appBar != null
11490
? appBar.type == "AppBar"
11591
? widgetsDesign == PageDesign.cupertino
@@ -126,30 +102,30 @@ class _PageletControlState extends State<PageletControl> {
126102
backgroundColor: widget.control.getColor("bgcolor", context) ??
127103
CupertinoTheme.of(context).scaffoldBackgroundColor,
128104
appBar: bar is AppBarControl ? bar : null,
129-
drawer:
130-
drawer != null ? NavigationDrawerControl(control: drawer) : null,
105+
drawer: drawer != null ? ControlWidget(control: drawer) : null,
131106
onDrawerChanged: (opened) {
132107
if (drawer != null && !opened) {
133-
widget.control
134-
.updateProperties({"drawer_opened": false}, python: false);
135108
dismissDrawer(drawer.id);
136109
}
137110
},
138-
endDrawer: endDrawer != null
139-
? NavigationDrawerControl(control: endDrawer)
140-
: null,
111+
endDrawer: endDrawer != null ? ControlWidget(control: endDrawer) : null,
141112
onEndDrawerChanged: (opened) {
142113
if (endDrawer != null && !opened) {
143-
widget.control
144-
.updateProperties({"end_drawer_opened": false}, python: false);
145114
dismissDrawer(endDrawer.id);
146115
}
147116
},
148117
body: content,
149118
bottomNavigationBar: bnb,
150119
bottomSheet: bottomSheet,
151120
floatingActionButton: fab,
152-
floatingActionButtonLocation: fabLocation);
121+
floatingActionButtonLocation: widget.control
122+
.getFloatingActionButtonLocation("floating_action_button_location",
123+
FloatingActionButtonLocation.endFloat));
124+
125+
if (hasDrawer) {
126+
// Clip to page bounds so the drawer animation stays hidden outside the pagelet.
127+
scaffold = ClipRect(child: scaffold);
128+
}
153129

154130
if (bar is CupertinoAppBarControl) {
155131
scaffold = CupertinoPageScaffold(

packages/flet/lib/src/controls/scrollable_control.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ class _ScrollableControlState extends State<ScrollableControl>
4242

4343
Future<dynamic> _invokeMethod(String name, dynamic args) async {
4444
debugPrint("ScrollableControl.$name($args)");
45-
var offset = parseDouble(args["offset"]);
46-
var delta = parseDouble(args["delta"]);
47-
var scrollKey = parseKey(args["scroll_key"]);
48-
var globalKey = scrollKey != null
49-
? widget.control.backend.globalKeys[scrollKey.toString()]
50-
: null;
5145
switch (name) {
5246
case "scroll_to":
47+
var offset = parseDouble(args["offset"]);
48+
var delta = parseDouble(args["delta"]);
49+
var scrollKey = parseKey(args["scroll_key"]);
50+
var globalKey = scrollKey != null
51+
? widget.control.backend.globalKeys[scrollKey.toString()]
52+
: null;
5353
var duration = parseDuration(args["duration"], Duration.zero)!;
5454
var curve = parseCurve(args["curve"], Curves.ease)!;
5555
if (globalKey != null) {

packages/flet/lib/src/controls/view.dart

Lines changed: 47 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'dart:async';
22

3-
import 'package:collection/collection.dart';
43
import 'package:flutter/cupertino.dart';
54
import 'package:flutter/material.dart';
65
import 'package:flutter/services.dart';
@@ -22,7 +21,6 @@ import '../utils/theme.dart';
2221
import '../widgets/loading_page.dart';
2322
import '../widgets/page_context.dart';
2423
import '../widgets/page_media.dart';
25-
import '../widgets/scaffold_key_provider.dart';
2624
import 'app_bar.dart';
2725
import 'cupertino_app_bar.dart';
2826
import 'scroll_notification_control.dart';
@@ -72,33 +70,32 @@ class _ViewControlState extends State<ViewControl> {
7270
Future<dynamic> _invokeMethod(String name, dynamic args) async {
7371
debugPrint("View.$name($args)");
7472
switch (name) {
73+
case "show_drawer":
74+
_scaffoldKey.currentState?.openDrawer();
75+
break;
76+
case "close_drawer":
77+
_scaffoldKey.currentState?.closeDrawer();
78+
break;
79+
case "show_end_drawer":
80+
_scaffoldKey.currentState?.openEndDrawer();
81+
break;
82+
case "close_end_drawer":
83+
_scaffoldKey.currentState?.closeEndDrawer();
84+
break;
7585
case "confirm_pop":
7686
if (_popCompleter != null && !_popCompleter!.isCompleted) {
7787
_popCompleter?.complete(args["should_pop"]);
7888
}
89+
break;
7990
}
8091
}
8192

8293
void _overlayOrDialogsChanged() {
8394
setState(() {});
8495
}
8596

86-
Future<void> _dismissDrawer(Control drawer, FletBackend backend) async {
87-
await Future.delayed(const Duration(milliseconds: 250));
88-
backend.updateControl(drawer.id, {"open": false});
89-
backend.triggerControlEvent(drawer, "dismiss");
90-
}
91-
92-
void _openDrawers(Control? drawer, Control? endDrawer) {
93-
if (drawer != null &&
94-
drawer.getBool("open", false) == true &&
95-
_scaffoldKey.currentState?.isDrawerOpen == false) {
96-
_scaffoldKey.currentState?.openDrawer();
97-
} else if (endDrawer != null &&
98-
endDrawer.getBool("open", false) == true &&
99-
_scaffoldKey.currentState?.isEndDrawerOpen == false) {
100-
_scaffoldKey.currentState?.openEndDrawer();
101-
}
97+
Future<void> _dismissDrawer(int drawerId) async {
98+
widget.control.backend.triggerControlEventById(drawerId, "dismiss");
10299
}
103100

104101
@override
@@ -156,20 +153,17 @@ class _ViewControlState extends State<ViewControl> {
156153
var overlayControls = _overlay?.children("controls");
157154
var dialogControls = _dialogs?.children("controls");
158155

159-
Control? drawer = dialogControls?.firstWhereOrNull(
160-
(c) => c.type == "NavigationDrawer" && c.get("position") != "end");
161-
Control? endDrawer = dialogControls?.firstWhereOrNull(
162-
(c) => c.type == "NavigationDrawer" && c.get("position") == "end");
156+
var drawer = widget.control.child("drawer");
157+
var endDrawer = widget.control.child("end_drawer");
163158

164159
var isRootView = control.id == pageViews.first.id;
165160

166161
if (overlayControls != null && dialogControls != null) {
167162
if (control.id == pageViews.last.id) {
168163
overlayWidgets
169164
.addAll(overlayControls.map((c) => ControlWidget(control: c)));
170-
overlayWidgets.addAll(dialogControls
171-
.where((dialog) => dialog.type != "NavigationDrawer")
172-
.map((c) => ControlWidget(control: c)));
165+
overlayWidgets
166+
.addAll(dialogControls.map((c) => ControlWidget(control: c)));
173167
overlayWidgets.add(PageMedia(view: widget.control.parent));
174168
}
175169

@@ -179,10 +173,6 @@ class _ViewControlState extends State<ViewControl> {
179173
}
180174
}
181175

182-
WidgetsBinding.instance.addPostFrameCallback((_) {
183-
_openDrawers(drawer, endDrawer);
184-
});
185-
186176
Widget body = Stack(children: [
187177
SizedBox.expand(
188178
child: Container(
@@ -202,37 +192,34 @@ class _ViewControlState extends State<ViewControl> {
202192
: parseTheme(
203193
control.parent!.get("theme"), context, Brightness.dark);
204194

205-
Widget scaffold = ScaffoldKeyProvider(
206-
scaffoldKey: _scaffoldKey,
207-
child: Scaffold(
208-
key: appBarWidget == null || appBarWidget is AppBarControl
209-
? _scaffoldKey
210-
: null,
211-
backgroundColor: control.getColor("bgcolor", context) ??
212-
((pageData?.widgetsDesign == PageDesign.cupertino)
213-
? CupertinoTheme.of(context).scaffoldBackgroundColor
214-
: Theme.of(context).scaffoldBackgroundColor),
215-
appBar: appBarWidget is AppBarControl ? appBarWidget : null,
216-
drawer: drawer != null ? ControlWidget(control: drawer) : null,
217-
onDrawerChanged: (opened) {
218-
if (!opened) {
219-
_dismissDrawer(drawer!, FletBackend.of(context));
220-
}
221-
},
222-
endDrawer: endDrawer != null ? ControlWidget(control: endDrawer) : null,
223-
onEndDrawerChanged: (opened) {
224-
if (!opened) {
225-
_dismissDrawer(endDrawer!, FletBackend.of(context));
226-
}
227-
},
228-
body: body,
229-
bottomNavigationBar: control.buildWidget("navigation_bar") ??
230-
control.buildWidget("bottom_appbar"),
231-
floatingActionButton: control.buildWidget("floating_action_button"),
232-
floatingActionButtonLocation: control.getFloatingActionButtonLocation(
233-
"floating_action_button_location",
234-
FloatingActionButtonLocation.endFloat),
235-
),
195+
Widget scaffold = Scaffold(
196+
key: appBarWidget == null || appBarWidget is AppBarControl
197+
? _scaffoldKey
198+
: null,
199+
backgroundColor: control.getColor("bgcolor", context) ??
200+
((pageData?.widgetsDesign == PageDesign.cupertino)
201+
? CupertinoTheme.of(context).scaffoldBackgroundColor
202+
: Theme.of(context).scaffoldBackgroundColor),
203+
appBar: appBarWidget is AppBarControl ? appBarWidget : null,
204+
drawer: drawer != null ? ControlWidget(control: drawer) : null,
205+
onDrawerChanged: (opened) {
206+
if (!opened) {
207+
_dismissDrawer(drawer!.id);
208+
}
209+
},
210+
endDrawer: endDrawer != null ? ControlWidget(control: endDrawer) : null,
211+
onEndDrawerChanged: (opened) {
212+
if (!opened) {
213+
_dismissDrawer(endDrawer!.id);
214+
}
215+
},
216+
body: body,
217+
bottomNavigationBar: control.buildWidget("navigation_bar") ??
218+
control.buildWidget("bottom_appbar"),
219+
floatingActionButton: control.buildWidget("floating_action_button"),
220+
floatingActionButtonLocation: control.getFloatingActionButtonLocation(
221+
"floating_action_button_location",
222+
FloatingActionButtonLocation.endFloat),
236223
);
237224

238225
var systemOverlayStyle =

0 commit comments

Comments
 (0)