Skip to content

Commit 0e60f56

Browse files
committed
NavigationRail control
1 parent 56b18a7 commit 0e60f56

File tree

4 files changed

+235
-11
lines changed

4 files changed

+235
-11
lines changed

client/lib/controls/create_control.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import 'app_bar.dart';
1+
import 'navigation_rail.dart';
2+
23
import 'package:flutter/material.dart';
34
import 'package:flutter_redux/flutter_redux.dart';
45

@@ -226,6 +227,12 @@ Widget createControl(Control? parent, String id, bool parentDisabled) {
226227
control: controlView.control,
227228
children: controlView.children,
228229
parentDisabled: parentDisabled);
230+
case ControlType.navigationRail:
231+
return NavigationRailControl(
232+
parent: parent,
233+
control: controlView.control,
234+
children: controlView.children,
235+
parentDisabled: parentDisabled);
229236
default:
230237
throw Exception("Unknown control type: ${controlView.control.type}");
231238
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import 'package:flet_view/utils/icons.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_redux/flutter_redux.dart';
4+
5+
import '../actions.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/edge_insets.dart';
12+
import '../web_socket_client.dart';
13+
import 'create_control.dart';
14+
15+
class NavigationRailControl extends StatefulWidget {
16+
final Control? parent;
17+
final Control control;
18+
final List<Control> children;
19+
final bool parentDisabled;
20+
21+
const NavigationRailControl(
22+
{Key? key,
23+
this.parent,
24+
required this.control,
25+
required this.children,
26+
required this.parentDisabled})
27+
: super(key: key);
28+
29+
@override
30+
State<NavigationRailControl> createState() => _NavigationRailControlState();
31+
}
32+
33+
class _NavigationRailControlState extends State<NavigationRailControl> {
34+
List<String> _destIndex = [];
35+
String? _value;
36+
int? _selectedIndex;
37+
dynamic _dispatch;
38+
39+
@override
40+
void initState() {
41+
super.initState();
42+
_destIndex = widget.children
43+
.where((c) => c.isVisible && c.name == null)
44+
.map((c) => c.attrString("key") ?? c.attrString("label", "")!)
45+
.toList();
46+
}
47+
48+
void _destinationChanged(int index) {
49+
_selectedIndex = index;
50+
var value = _destIndex[index];
51+
if (_value != value) {
52+
debugPrint("Selected dest: $value");
53+
List<Map<String, String>> props = [
54+
{"i": widget.control.id, "value": value}
55+
];
56+
_dispatch(
57+
UpdateControlPropsAction(UpdateControlPropsPayload(props: props)));
58+
ws.updateControlProps(props: props);
59+
ws.pageEventFromWeb(
60+
eventTarget: widget.control.id,
61+
eventName: "change",
62+
eventData: value);
63+
}
64+
_value = value;
65+
}
66+
67+
@override
68+
Widget build(BuildContext context) {
69+
debugPrint("NavigationRailControl build: ${widget.control.id}");
70+
71+
var destIndex = widget.children
72+
.where((c) => c.isVisible && c.name == null)
73+
.map((c) => c.attrString("key") ?? c.attrString("label", "")!)
74+
.toList();
75+
if (destIndex.length != _destIndex.length ||
76+
!destIndex.every((item) => _destIndex.contains(item))) {
77+
_destIndex = destIndex;
78+
}
79+
80+
bool disabled = widget.control.isDisabled || widget.parentDisabled;
81+
82+
String? value = widget.control.attrString("value");
83+
84+
if (_value != value) {
85+
_value = value;
86+
87+
int idx = _destIndex.indexOf(_value ?? "");
88+
if (idx != -1) {
89+
_selectedIndex = idx;
90+
}
91+
}
92+
93+
NavigationRailLabelType? labelType = NavigationRailLabelType.values
94+
.firstWhere(
95+
(a) =>
96+
a.name.toLowerCase() ==
97+
widget.control.attrString("labelType", "")!.toLowerCase(),
98+
orElse: () => NavigationRailLabelType.all);
99+
100+
var leadingCtrls = widget.children.where((c) => c.name == "leading");
101+
var trailingCtrls = widget.children.where((c) => c.name == "trailing");
102+
103+
var extended = widget.control.attrBool("extended", false)!;
104+
105+
var rail = StoreConnector<AppState, ControlsViewModel>(
106+
distinct: true,
107+
converter: (store) => ControlsViewModel.fromStore(
108+
store,
109+
widget.children
110+
.where((c) => c.isVisible && c.name == null)
111+
.map((c) => c.id)),
112+
builder: (content, viewModel) {
113+
_dispatch = viewModel.dispatch;
114+
115+
return NavigationRail(
116+
labelType: extended ? NavigationRailLabelType.none : labelType,
117+
extended: extended,
118+
minWidth: widget.control.attrDouble("minWidth"),
119+
minExtendedWidth: widget.control.attrDouble("minExtendedWidth"),
120+
groupAlignment: widget.control.attrDouble("groupAlignment"),
121+
backgroundColor: HexColor.fromString(
122+
Theme.of(context), widget.control.attrString("bgColor", "")!),
123+
leading: leadingCtrls.isNotEmpty
124+
? createControl(
125+
widget.control, leadingCtrls.first.id, disabled)
126+
: null,
127+
trailing: trailingCtrls.isNotEmpty
128+
? createControl(
129+
widget.control, trailingCtrls.first.id, disabled)
130+
: null,
131+
selectedIndex: _selectedIndex,
132+
onDestinationSelected: _destinationChanged,
133+
destinations: viewModel.controlViews.map((destView) {
134+
var label = destView.control.attrString("label", "")!;
135+
var labelContentCtrls =
136+
destView.children.where((c) => c.name == "label_content");
137+
138+
var icon =
139+
getMaterialIcon(destView.control.attrString("icon", "")!);
140+
var iconContentCtrls =
141+
destView.children.where((c) => c.name == "icon_content");
142+
143+
var selectedIcon = getMaterialIcon(
144+
destView.control.attrString("selectedIcon", "")!);
145+
var selectedIconContentCtrls = destView.children
146+
.where((c) => c.name == "selected_icon_content");
147+
148+
return NavigationRailDestination(
149+
padding: parseEdgeInsets(destView.control, "padding"),
150+
icon: iconContentCtrls.isNotEmpty
151+
? createControl(destView.control,
152+
iconContentCtrls.first.id, disabled)
153+
: Icon(icon),
154+
selectedIcon: selectedIconContentCtrls.isNotEmpty
155+
? createControl(destView.control,
156+
selectedIconContentCtrls.first.id, disabled)
157+
: selectedIcon != null
158+
? Icon(selectedIcon)
159+
: null,
160+
label: labelContentCtrls.isNotEmpty
161+
? createControl(destView.control,
162+
labelContentCtrls.first.id, disabled)
163+
: Text(label));
164+
}).toList());
165+
});
166+
167+
return constrainedControl(rail, widget.parent, widget.control);
168+
}
169+
}

sdk/python/flet/navigation_rail.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __init__(
2727
padding: PaddingValue = None,
2828
):
2929
Control.__init__(self, ref=ref)
30-
assert key, "key must be specified"
30+
assert key or label, "key or label must be specified"
3131
self.key = key
3232
self.label = label
3333
self.icon = icon
@@ -155,6 +155,7 @@ def __init__(
155155
# NavigationRail-specific
156156
destinations: List[NavigationRailDestination] = None,
157157
value: str = None,
158+
extended: bool = None,
158159
label_type: NavigationRailLabelType = None,
159160
bgcolor: str = None,
160161
leading: Control = None,
@@ -179,6 +180,7 @@ def __init__(
179180

180181
self.destinations = destinations
181182
self.value = value
183+
self.extended = extended
182184
self.label_type = label_type
183185
self.bgcolor = bgcolor
184186
self.__leading = None
@@ -233,15 +235,7 @@ def value(self):
233235
@value.setter
234236
@beartype
235237
def value(self, value: Optional[str]):
236-
if not value:
237-
assert (
238-
not self.destinations
239-
), "Setting an empty value is only allowed if you have no tabs"
240-
else:
241-
assert any(
242-
value in keys for keys in [(dest.key) for dest in self.destinations]
243-
), f"'{value}' is not a key for any tab"
244-
self._set_attr("value", value or "")
238+
self._set_attr("value", value)
245239

246240
# label_type
247241
@property

sdk/python/playground/rail-test.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import logging
2+
from time import sleep
3+
4+
import flet
5+
from flet import (
6+
Column,
7+
FloatingActionButton,
8+
Icon,
9+
NavigationRail,
10+
NavigationRailDestination,
11+
Page,
12+
Row,
13+
Text,
14+
icons,
15+
)
16+
from flet.divider import Divider
17+
18+
logging.basicConfig(level=logging.DEBUG)
19+
20+
21+
def main(page: Page):
22+
def fab_click(e):
23+
rail.value = "third"
24+
page.update()
25+
26+
rail = NavigationRail(
27+
value="Second",
28+
label_type="all",
29+
# extended=True,
30+
min_width=100,
31+
min_extended_width=400,
32+
leading=FloatingActionButton(icon=icons.CREATE, text="Add", on_click=fab_click),
33+
trailing=Text("Something"),
34+
group_alignment=-0.9,
35+
destinations=[
36+
NavigationRailDestination(
37+
icon=icons.FAVORITE_BORDER, selected_icon=icons.FAVORITE, label="First"
38+
),
39+
NavigationRailDestination(
40+
icon_content=Icon(icons.BOOKMARK_BORDER),
41+
selected_icon_content=Icon(icons.BOOKMARK),
42+
label="Second",
43+
),
44+
NavigationRailDestination(
45+
key="third", icon=icons.STAR_BORDER, label_content=Text("Third")
46+
),
47+
],
48+
on_change=lambda e: print(f"Selected tab: {e.control.value}"),
49+
)
50+
51+
page.add(Row([rail], expand=True))
52+
53+
54+
flet.app(name="test1", port=8550, target=main, view=flet.WEB_BROWSER)

0 commit comments

Comments
 (0)