Skip to content

Commit 5cde674

Browse files
NavigationDrawer control (#2059)
* Create navigation_drawer.py * added end_drawer to view and page * navigation_drawer.dart * NavigationDrawer prototype * NavigationDrawerDestination icon property * icon_content for destination * destinations changed to controls * removed bgColor property for NavigationDrawerDestination as it doesn't work in flutter flutter/flutter#138105 * Update navigation_drawer.dart * selected_icon and selected_icon_content * elevation property * indicator_color property * indicator_shape property * shadow_color property * surface_tint_color property * refactor * added elevation and shadow_color to NavigationBar * indicator_color, indicator_shape, surface_tint_color, shadow_color, elevation for NavigationBar * show_drawer, show_end_drawer, close_drawer, close_end_drawer for page * on_dismiss for NavigationDrawer * updated comments --------- Co-authored-by: Feodor Fitsner <[email protected]>
1 parent 2a2d063 commit 5cde674

File tree

8 files changed

+771
-17
lines changed

8 files changed

+771
-17
lines changed

package/lib/src/controls/navigation_bar.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import '../protocol/update_control_props_payload.dart';
1111
import '../utils/colors.dart';
1212
import '../utils/icons.dart';
1313
import 'create_control.dart';
14+
import '../utils/borders.dart';
1415

1516
class NavigationBarControl extends StatefulWidget {
1617
final Control? parent;
@@ -79,6 +80,14 @@ class _NavigationBarControlState extends State<NavigationBarControl> {
7980
labelBehavior: labelBehavior,
8081
height: widget.control.attrDouble("height"),
8182
elevation: widget.control.attrDouble("elevation"),
83+
shadowColor: HexColor.fromString(Theme.of(context),
84+
widget.control.attrString("shadowColor", "")!),
85+
surfaceTintColor: HexColor.fromString(Theme.of(context),
86+
widget.control.attrString("surfaceTintColor", "")!),
87+
indicatorColor: HexColor.fromString(Theme.of(context),
88+
widget.control.attrString("indicatorColor", "")!),
89+
indicatorShape:
90+
parseOutlinedBorder(widget.control, "indicatorShape"),
8291
backgroundColor: HexColor.fromString(
8392
Theme.of(context), widget.control.attrString("bgColor", "")!),
8493
selectedIndex: _selectedIndex,
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_redux/flutter_redux.dart';
3+
4+
import '../actions.dart';
5+
import '../flet_app_services.dart';
6+
import '../models/app_state.dart';
7+
import '../models/control.dart';
8+
import '../models/controls_view_model.dart';
9+
import '../protocol/update_control_props_payload.dart';
10+
import '../utils/colors.dart';
11+
import '../utils/icons.dart';
12+
import 'create_control.dart';
13+
import '../utils/borders.dart';
14+
import '../utils/edge_insets.dart';
15+
16+
class NavigationDrawerControl extends StatefulWidget {
17+
final Control? parent;
18+
final Control control;
19+
final List<Control> children;
20+
final bool parentDisabled;
21+
final dynamic dispatch;
22+
23+
const NavigationDrawerControl(
24+
{Key? key,
25+
this.parent,
26+
required this.control,
27+
required this.children,
28+
required this.parentDisabled,
29+
required this.dispatch})
30+
: super(key: key);
31+
32+
@override
33+
State<NavigationDrawerControl> createState() =>
34+
_NavigationDrawerControlState();
35+
}
36+
37+
class _NavigationDrawerControlState extends State<NavigationDrawerControl> {
38+
int _selectedIndex = 0;
39+
40+
void _destinationChanged(int index) {
41+
_selectedIndex = index;
42+
debugPrint("Selected index: $_selectedIndex");
43+
List<Map<String, String>> props = [
44+
{"i": widget.control.id, "selectedindex": _selectedIndex.toString()}
45+
];
46+
widget.dispatch(
47+
UpdateControlPropsAction(UpdateControlPropsPayload(props: props)));
48+
final server = FletAppServices.of(context).server;
49+
server.updateControlProps(props: props);
50+
server.sendPageEvent(
51+
eventTarget: widget.control.id,
52+
eventName: "change",
53+
eventData: _selectedIndex.toString());
54+
}
55+
56+
@override
57+
Widget build(BuildContext context) {
58+
debugPrint("NavigationDrawerControl build: ${widget.control.id}");
59+
60+
bool disabled = widget.control.isDisabled || widget.parentDisabled;
61+
var selectedIndex = widget.control.attrInt("selectedIndex", 0)!;
62+
63+
if (_selectedIndex != selectedIndex) {
64+
_selectedIndex = selectedIndex;
65+
}
66+
67+
var navDrawer = StoreConnector<AppState, ControlsViewModel>(
68+
distinct: true,
69+
converter: (store) => ControlsViewModel.fromStore(
70+
store,
71+
widget.children
72+
.where((c) => c.isVisible && c.name == null)
73+
.map((c) => c.id)),
74+
builder: (content, viewModel) {
75+
List<Widget> children = viewModel.controlViews.map((destView) {
76+
if (destView.control.type == "navigationdrawerdestination") {
77+
var icon =
78+
getMaterialIcon(destView.control.attrString("icon", "")!);
79+
var iconContentCtrls =
80+
destView.children.where((c) => c.name == "icon_content");
81+
var selectedIcon = getMaterialIcon(
82+
destView.control.attrString("selectedIcon", "")!);
83+
var selectedIconContentCtrls = destView.children
84+
.where((c) => c.name == "selected_icon_content");
85+
return NavigationDrawerDestination(
86+
// backgroundColor: HexColor.fromString(Theme.of(context),
87+
// destView.control.attrString("bgColor", "")!),
88+
// flutter issue https://github.com/flutter/flutter/issues/138105
89+
icon: iconContentCtrls.isNotEmpty
90+
? createControl(
91+
destView.control, iconContentCtrls.first.id, disabled)
92+
: Icon(icon),
93+
label: Text(destView.control.attrString("label", "")!),
94+
selectedIcon: selectedIconContentCtrls.isNotEmpty
95+
? createControl(destView.control,
96+
selectedIconContentCtrls.first.id, disabled)
97+
: selectedIcon != null
98+
? Icon(selectedIcon)
99+
: null,
100+
);
101+
} else {
102+
return createControl(
103+
widget.control, destView.control.id, disabled);
104+
}
105+
}).toList();
106+
return NavigationDrawer(
107+
elevation: widget.control.attrDouble("elevation"),
108+
indicatorColor: HexColor.fromString(Theme.of(context),
109+
widget.control.attrString("indicatorColor", "")!),
110+
indicatorShape:
111+
parseOutlinedBorder(widget.control, "indicatorShape"),
112+
backgroundColor: HexColor.fromString(
113+
Theme.of(context), widget.control.attrString("bgColor", "")!),
114+
selectedIndex: _selectedIndex,
115+
shadowColor: HexColor.fromString(Theme.of(context),
116+
widget.control.attrString("shadowColor", "")!),
117+
surfaceTintColor: HexColor.fromString(Theme.of(context),
118+
widget.control.attrString("surfaceTintColor", "")!),
119+
tilePadding: parseEdgeInsets(widget.control, "tilePadding") ??
120+
const EdgeInsets.symmetric(horizontal: 12.0),
121+
onDestinationSelected: _destinationChanged,
122+
children: children,
123+
);
124+
});
125+
126+
return navDrawer;
127+
}
128+
}

package/lib/src/controls/page.dart

Lines changed: 119 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import '../models/page_args_model.dart';
1717
import '../models/page_media_view_model.dart';
1818
import '../models/routes_view_model.dart';
1919
import '../protocol/keyboard_event.dart';
20+
import '../protocol/update_control_props_payload.dart';
2021
import '../routing/route_parser.dart';
2122
import '../routing/route_state.dart';
2223
import '../routing/router_delegate.dart';
@@ -34,6 +35,7 @@ import '../widgets/page_media.dart';
3435
import '../widgets/window_media.dart';
3536
import 'app_bar.dart';
3637
import 'create_control.dart';
38+
import 'navigation_drawer.dart';
3739
import 'scroll_notification_control.dart';
3840
import 'scrollable_control.dart';
3941

@@ -90,6 +92,8 @@ class _PageControlState extends State<PageControl> {
9092
late final RouteParser _routeParser;
9193
String? _prevViewRoutes;
9294
bool _keyboardHandlerSubscribed = false;
95+
bool? _drawerOpened;
96+
bool? _endDrawerOpened;
9397

9498
@override
9599
void initState() {
@@ -598,6 +602,8 @@ class _PageControlState extends State<PageControl> {
598602
Control? appBar;
599603
Control? fab;
600604
Control? navBar;
605+
Control? drawer;
606+
Control? endDrawer;
601607
List<Widget> controls = [];
602608
bool firstControl = true;
603609

@@ -611,6 +617,13 @@ class _PageControlState extends State<PageControl> {
611617
} else if (ctrl.type == "navigationbar") {
612618
navBar = ctrl;
613619
continue;
620+
} else if (ctrl.type == "navigationdrawer" &&
621+
ctrl.name == "start") {
622+
drawer = ctrl;
623+
continue;
624+
} else if (ctrl.type == "navigationdrawer" && ctrl.name == "end") {
625+
endDrawer = ctrl;
626+
continue;
614627
}
615628
// spacer between displayed controls
616629
else if (spacing > 0 &&
@@ -626,10 +639,8 @@ class _PageControlState extends State<PageControl> {
626639
controls.add(createControl(control, ctrl.id, control.isDisabled));
627640
}
628641

629-
List<String> childIds = [];
630-
if (appBar != null) {
631-
childIds.add(appBar.id);
632-
}
642+
List<String> childIds =
643+
[appBar?.id, drawer?.id, endDrawer?.id].whereNotNull().toList();
633644

634645
final textDirection = parent.attrBool("rtl", false)!
635646
? TextDirection.rtl
@@ -651,10 +662,12 @@ class _PageControlState extends State<PageControl> {
651662
builder: (context, childrenViews) {
652663
debugPrint("Route view StoreConnector build: $viewId");
653664

654-
var appBarView =
655-
appBar != null && childrenViews.controlViews.isNotEmpty
656-
? childrenViews.controlViews.last
657-
: null;
665+
var appBarView = childrenViews.controlViews.firstWhereOrNull(
666+
(v) => v.control.id == (appBar?.id ?? ""));
667+
var drawerView = childrenViews.controlViews.firstWhereOrNull(
668+
(v) => v.control.id == (drawer?.id ?? ""));
669+
var endDrawerView = childrenViews.controlViews.firstWhereOrNull(
670+
(v) => v.control.id == (endDrawer?.id ?? ""));
658671

659672
var column = Column(
660673
mainAxisAlignment: mainAlignment,
@@ -673,7 +686,55 @@ class _PageControlState extends State<PageControl> {
673686
ScrollNotificationControl(control: control, child: child);
674687
}
675688

689+
GlobalKey<ScaffoldState>? scaffoldKey =
690+
FletAppServices.of(context).globalKeys["_scaffold"]
691+
as GlobalKey<ScaffoldState>?;
692+
if (scaffoldKey == null) {
693+
scaffoldKey = GlobalKey<ScaffoldState>();
694+
FletAppServices.of(context).globalKeys["_scaffold"] =
695+
scaffoldKey;
696+
}
697+
698+
WidgetsBinding.instance.addPostFrameCallback((_) {
699+
if (drawerView != null) {
700+
if (drawerView.control.attrBool("open", false)! &&
701+
_drawerOpened != true) {
702+
if (scaffoldKey?.currentState?.isEndDrawerOpen == true) {
703+
scaffoldKey?.currentState?.closeEndDrawer();
704+
}
705+
Future.delayed(const Duration(milliseconds: 1))
706+
.then((value) {
707+
scaffoldKey?.currentState?.openDrawer();
708+
_drawerOpened = true;
709+
});
710+
} else if (!drawerView.control.attrBool("open", false)! &&
711+
_drawerOpened == true) {
712+
scaffoldKey?.currentState?.closeDrawer();
713+
_drawerOpened = false;
714+
}
715+
}
716+
if (endDrawerView != null) {
717+
if (endDrawerView.control.attrBool("open", false)! &&
718+
_endDrawerOpened != true) {
719+
if (scaffoldKey?.currentState?.isDrawerOpen == true) {
720+
scaffoldKey?.currentState?.closeDrawer();
721+
}
722+
Future.delayed(const Duration(milliseconds: 1))
723+
.then((value) {
724+
scaffoldKey?.currentState?.openEndDrawer();
725+
_endDrawerOpened = true;
726+
});
727+
} else if (!endDrawerView.control
728+
.attrBool("open", false)! &&
729+
_endDrawerOpened == true) {
730+
scaffoldKey?.currentState?.closeEndDrawer();
731+
_endDrawerOpened = false;
732+
}
733+
}
734+
});
735+
676736
var scaffold = Scaffold(
737+
key: scaffoldKey,
677738
backgroundColor: HexColor.fromString(
678739
Theme.of(context), control.attrString("bgcolor", "")!),
679740
appBar: appBarView != null
@@ -685,6 +746,56 @@ class _PageControlState extends State<PageControl> {
685746
height: appBarView.control
686747
.attrDouble("toolbarHeight", kToolbarHeight)!)
687748
: null,
749+
drawer: drawerView != null
750+
? NavigationDrawerControl(
751+
control: drawerView.control,
752+
children: drawerView.children,
753+
parentDisabled: control.isDisabled,
754+
dispatch: widget.dispatch,
755+
)
756+
: null,
757+
onDrawerChanged: (opened) {
758+
if (drawerView != null && !opened) {
759+
_drawerOpened = false;
760+
List<Map<String, String>> props = [
761+
{"i": drawerView.control.id, "open": "false"}
762+
];
763+
widget.dispatch(UpdateControlPropsAction(
764+
UpdateControlPropsPayload(props: props)));
765+
FletAppServices.of(context)
766+
.server
767+
.updateControlProps(props: props);
768+
FletAppServices.of(context).server.sendPageEvent(
769+
eventTarget: drawerView.control.id,
770+
eventName: "dismiss",
771+
eventData: "");
772+
}
773+
},
774+
endDrawer: endDrawerView != null
775+
? NavigationDrawerControl(
776+
control: endDrawerView.control,
777+
children: endDrawerView.children,
778+
parentDisabled: control.isDisabled,
779+
dispatch: widget.dispatch,
780+
)
781+
: null,
782+
onEndDrawerChanged: (opened) {
783+
if (endDrawerView != null && !opened) {
784+
_endDrawerOpened = false;
785+
List<Map<String, String>> props = [
786+
{"i": endDrawerView.control.id, "open": "false"}
787+
];
788+
widget.dispatch(UpdateControlPropsAction(
789+
UpdateControlPropsPayload(props: props)));
790+
FletAppServices.of(context)
791+
.server
792+
.updateControlProps(props: props);
793+
FletAppServices.of(context).server.sendPageEvent(
794+
eventTarget: endDrawerView.control.id,
795+
eventName: "dismiss",
796+
eventData: "");
797+
}
798+
},
688799
body: Stack(children: [
689800
SizedBox.expand(
690801
child: Container(

sdk/python/packages/flet-core/src/flet_core/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,4 @@
207207
from flet_core.webview import WebView
208208
from flet_core.range_slider import RangeSlider
209209
from flet_core.badge import Badge
210+
from flet_core.navigation_drawer import NavigationDrawer, NavigationDrawerDestination

0 commit comments

Comments
 (0)