Skip to content

Commit b1fbf87

Browse files
SearchBar control (#2212)
* initial commit * attempt to add dismiss method * Create SearchController on init * SearchAnchor.bar -> SearchAnchor + SearchBar * open_view and close_view: sync and async * add value prop * add value default --------- Co-authored-by: Feodor Fitsner <[email protected]>
1 parent dc1f887 commit b1fbf87

File tree

5 files changed

+654
-2
lines changed

5 files changed

+654
-2
lines changed

client/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,15 @@ SPEC CHECKSUMS:
9797
audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40
9898
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
9999
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
100-
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
100+
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
101101
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
102102
integration_test: 13825b8a9334a850581300559b8839134b124670
103103
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
104104
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
105105
sensors_plus: 5717760720f7e6acd96fdbd75b7428f5ad755ec2
106106
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
107107
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
108-
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
108+
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
109109
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
110110

111111
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3

package/lib/src/controls/create_control.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:math';
22

33
import 'package:collection/collection.dart';
4+
import 'package:flet/src/controls/search_anchor.dart';
45
import 'package:flet/src/controls/segmented_button.dart';
56
import 'package:flutter/material.dart';
67
import 'package:flutter_redux/flutter_redux.dart';
@@ -483,6 +484,14 @@ Widget createWidget(Key? key, ControlViewModel controlView, Control? parent,
483484
control: controlView.control,
484485
children: controlView.children,
485486
parentDisabled: parentDisabled);
487+
case "searchbar":
488+
return SearchAnchorControl(
489+
key: key,
490+
parent: parent,
491+
control: controlView.control,
492+
children: controlView.children,
493+
parentDisabled: parentDisabled,
494+
dispatch: controlView.dispatch);
486495
case "checkbox":
487496
return CheckboxControl(
488497
key: key,
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import 'dart:convert';
2+
3+
import 'package:flutter/material.dart';
4+
import 'package:flutter_redux/flutter_redux.dart';
5+
6+
import '../actions.dart';
7+
import '../flet_app_services.dart';
8+
import '../models/app_state.dart';
9+
import '../models/control.dart';
10+
import '../protocol/update_control_props_payload.dart';
11+
import '../utils/borders.dart';
12+
import '../utils/buttons.dart';
13+
import '../utils/colors.dart';
14+
import '../utils/text.dart';
15+
import 'create_control.dart';
16+
17+
class SearchAnchorControl extends StatefulWidget {
18+
final Control? parent;
19+
final Control control;
20+
final List<Control> children;
21+
final bool parentDisabled;
22+
final dynamic dispatch;
23+
24+
const SearchAnchorControl(
25+
{super.key,
26+
this.parent,
27+
required this.control,
28+
required this.children,
29+
required this.parentDisabled,
30+
required this.dispatch});
31+
32+
@override
33+
State<SearchAnchorControl> createState() => _SearchAnchorControlState();
34+
}
35+
36+
class _SearchAnchorControlState extends State<SearchAnchorControl> {
37+
late final SearchController _controller;
38+
String _value = "";
39+
40+
@override
41+
void initState() {
42+
super.initState();
43+
_controller = SearchController();
44+
_controller.addListener(_searchTextChanged);
45+
}
46+
47+
@override
48+
void dispose() {
49+
_controller.removeListener(_searchTextChanged);
50+
_controller.dispose();
51+
super.dispose();
52+
}
53+
54+
void _searchTextChanged() {
55+
debugPrint("_searchTextChanged: ${_controller.text}");
56+
List<Map<String, String>> props = [
57+
{"i": widget.control.id, "value": _controller.text}
58+
];
59+
widget.dispatch(
60+
UpdateControlPropsAction(UpdateControlPropsPayload(props: props)));
61+
FletAppServices.of(context).server.updateControlProps(props: props);
62+
}
63+
64+
@override
65+
Widget build(BuildContext context) {
66+
debugPrint("SearchAnchor build: ${widget.control.id}");
67+
bool disabled = widget.control.isDisabled || widget.parentDisabled;
68+
69+
debugPrint(widget.control.attrs.toString());
70+
71+
return StoreConnector<AppState, Function>(
72+
distinct: true,
73+
converter: (store) => store.dispatch,
74+
builder: (context, dispatch) {
75+
debugPrint("SearchAnchor StoreConnector build: ${widget.control.id}");
76+
77+
var value = widget.control.attrString("value");
78+
if (value != null) {
79+
_controller.text = value;
80+
}
81+
82+
bool onChange = widget.control.attrBool("onChange", false)!;
83+
bool onTap = widget.control.attrBool("onTap", false)!;
84+
bool onSubmit = widget.control.attrBool("onSubmit", false)!;
85+
86+
var suggestionCtrls =
87+
widget.children.where((c) => c.name == "controls" && c.isVisible);
88+
var barLeadingCtrls = widget.children
89+
.where((c) => c.name == "barLeading" && c.isVisible);
90+
var barTrailingCtrls = widget.children
91+
.where((c) => c.name == "barTrailing" && c.isVisible);
92+
var viewLeadingCtrls = widget.children
93+
.where((c) => c.name == "viewLeading" && c.isVisible);
94+
var viewTrailingCtrls = widget.children
95+
.where((c) => c.name == "viewTrailing" && c.isVisible);
96+
97+
var viewBgcolor = HexColor.fromString(
98+
Theme.of(context), widget.control.attrString("viewBgcolor", "")!);
99+
var dividerColor = HexColor.fromString(Theme.of(context),
100+
widget.control.attrString("dividerColor", "")!);
101+
102+
TextStyle? viewHeaderTextStyle = parseTextStyle(
103+
Theme.of(context), widget.control, "viewHeaderTextStyle");
104+
TextStyle? viewHintTextStyle = parseTextStyle(
105+
Theme.of(context), widget.control, "viewHintTextStyle");
106+
107+
var method = widget.control.attrString("method");
108+
109+
if (method != null) {
110+
debugPrint("SearchAnchor JSON method: $method");
111+
112+
var mj = json.decode(method);
113+
var name = mj["n"] as String;
114+
var params = Map<String, dynamic>.from(mj["p"] as Map);
115+
116+
if (name == "closeView") {
117+
WidgetsBinding.instance.addPostFrameCallback((_) {
118+
List<Map<String, String>> props = [
119+
{"i": widget.control.id, "method": ""}
120+
];
121+
widget.dispatch(UpdateControlPropsAction(
122+
UpdateControlPropsPayload(props: props)));
123+
FletAppServices.of(context)
124+
.server
125+
.updateControlProps(props: props);
126+
if (_controller.isOpen) {
127+
var text = params["text"].toString();
128+
setState(() {
129+
_controller.closeView(text);
130+
});
131+
}
132+
});
133+
} else if (name == "openView") {
134+
WidgetsBinding.instance.addPostFrameCallback((_) {
135+
List<Map<String, String>> props = [
136+
{"i": widget.control.id, "method": ""}
137+
];
138+
widget.dispatch(UpdateControlPropsAction(
139+
UpdateControlPropsPayload(props: props)));
140+
FletAppServices.of(context)
141+
.server
142+
.updateControlProps(props: props);
143+
if (!_controller.isOpen) {
144+
_controller.openView();
145+
}
146+
});
147+
}
148+
}
149+
150+
Widget anchor = SearchAnchor(
151+
searchController: _controller,
152+
headerHintStyle: viewHintTextStyle,
153+
headerTextStyle: viewHeaderTextStyle,
154+
viewSide: parseBorderSide(
155+
Theme.of(context), widget.control, "viewSide"),
156+
isFullScreen: widget.control.attrBool("fullScreen", false),
157+
viewBackgroundColor: viewBgcolor,
158+
dividerColor: dividerColor,
159+
viewHintText: widget.control.attrString("viewHintText"),
160+
viewElevation: widget.control.attrDouble("viewElevation"),
161+
viewShape: parseOutlinedBorder(widget.control, "viewShape"),
162+
viewTrailing: viewTrailingCtrls.isNotEmpty
163+
? viewTrailingCtrls.map((ctrl) {
164+
return createControl(widget.parent, ctrl.id, disabled);
165+
})
166+
: null,
167+
viewLeading: viewLeadingCtrls.isNotEmpty
168+
? createControl(
169+
widget.parent, viewLeadingCtrls.first.id, disabled)
170+
: null,
171+
builder: (BuildContext context, SearchController controller) {
172+
return SearchBar(
173+
controller: controller,
174+
hintText: widget.control.attrString("barHintText"),
175+
backgroundColor: parseMaterialStateColor(
176+
Theme.of(context), widget.control, "barBgcolor"),
177+
overlayColor: parseMaterialStateColor(
178+
Theme.of(context), widget.control, "barOverlayColor"),
179+
leading: barLeadingCtrls.isNotEmpty
180+
? createControl(
181+
widget.parent, barLeadingCtrls.first.id, disabled)
182+
: null,
183+
trailing: barTrailingCtrls.isNotEmpty
184+
? barTrailingCtrls.map((ctrl) {
185+
return createControl(
186+
widget.parent, ctrl.id, disabled);
187+
})
188+
: null,
189+
onTap: () {
190+
if (onTap) {
191+
FletAppServices.of(context).server.sendPageEvent(
192+
eventTarget: widget.control.id,
193+
eventName: "tap",
194+
eventData: "");
195+
}
196+
controller.openView();
197+
},
198+
onSubmitted: onSubmit
199+
? (String value) {
200+
FletAppServices.of(context).server.sendPageEvent(
201+
eventTarget: widget.control.id,
202+
eventName: "submit",
203+
eventData: value);
204+
}
205+
: null,
206+
onChanged: onChange
207+
? (String value) {
208+
FletAppServices.of(context).server.sendPageEvent(
209+
eventTarget: widget.control.id,
210+
eventName: "change",
211+
eventData: value);
212+
}
213+
: null,
214+
);
215+
},
216+
suggestionsBuilder:
217+
(BuildContext context, SearchController controller) {
218+
return suggestionCtrls.map((ctrl) {
219+
return createControl(widget.parent, ctrl.id, disabled);
220+
});
221+
});
222+
223+
return constrainedControl(
224+
context, anchor, widget.parent, widget.control);
225+
});
226+
}
227+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@
162162
from flet_core.row import Row
163163
from flet_core.safe_area import SafeArea
164164
from flet_core.scrollable_control import OnScrollEvent
165+
from flet_core.search_anchor import SearchBar
165166
from flet_core.segmented_button import Segment, SegmentedButton
166167
from flet_core.semantics import Semantics
167168
from flet_core.shader_mask import ShaderMask

0 commit comments

Comments
 (0)