Skip to content

Commit

Permalink
SearchBar control (#2212)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
ndonkoHenri and FeodorFitsner authored Dec 17, 2023
1 parent dc1f887 commit b1fbf87
Show file tree
Hide file tree
Showing 5 changed files with 654 additions and 2 deletions.
4 changes: 2 additions & 2 deletions client/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,15 @@ SPEC CHECKSUMS:
audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
integration_test: 13825b8a9334a850581300559b8839134b124670
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
sensors_plus: 5717760720f7e6acd96fdbd75b7428f5ad755ec2
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a

PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
Expand Down
9 changes: 9 additions & 0 deletions package/lib/src/controls/create_control.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:math';

import 'package:collection/collection.dart';
import 'package:flet/src/controls/search_anchor.dart';
import 'package:flet/src/controls/segmented_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
Expand Down Expand Up @@ -483,6 +484,14 @@ Widget createWidget(Key? key, ControlViewModel controlView, Control? parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled);
case "searchbar":
return SearchAnchorControl(
key: key,
parent: parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled,
dispatch: controlView.dispatch);
case "checkbox":
return CheckboxControl(
key: key,
Expand Down
227 changes: 227 additions & 0 deletions package/lib/src/controls/search_anchor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';

import '../actions.dart';
import '../flet_app_services.dart';
import '../models/app_state.dart';
import '../models/control.dart';
import '../protocol/update_control_props_payload.dart';
import '../utils/borders.dart';
import '../utils/buttons.dart';
import '../utils/colors.dart';
import '../utils/text.dart';
import 'create_control.dart';

class SearchAnchorControl extends StatefulWidget {
final Control? parent;
final Control control;
final List<Control> children;
final bool parentDisabled;
final dynamic dispatch;

const SearchAnchorControl(
{super.key,
this.parent,
required this.control,
required this.children,
required this.parentDisabled,
required this.dispatch});

@override
State<SearchAnchorControl> createState() => _SearchAnchorControlState();
}

class _SearchAnchorControlState extends State<SearchAnchorControl> {
late final SearchController _controller;
String _value = "";

@override
void initState() {
super.initState();
_controller = SearchController();
_controller.addListener(_searchTextChanged);
}

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

void _searchTextChanged() {
debugPrint("_searchTextChanged: ${_controller.text}");
List<Map<String, String>> props = [
{"i": widget.control.id, "value": _controller.text}
];
widget.dispatch(
UpdateControlPropsAction(UpdateControlPropsPayload(props: props)));
FletAppServices.of(context).server.updateControlProps(props: props);
}

@override
Widget build(BuildContext context) {
debugPrint("SearchAnchor build: ${widget.control.id}");
bool disabled = widget.control.isDisabled || widget.parentDisabled;

debugPrint(widget.control.attrs.toString());

return StoreConnector<AppState, Function>(
distinct: true,
converter: (store) => store.dispatch,
builder: (context, dispatch) {
debugPrint("SearchAnchor StoreConnector build: ${widget.control.id}");

var value = widget.control.attrString("value");
if (value != null) {
_controller.text = value;
}

bool onChange = widget.control.attrBool("onChange", false)!;
bool onTap = widget.control.attrBool("onTap", false)!;
bool onSubmit = widget.control.attrBool("onSubmit", false)!;

var suggestionCtrls =
widget.children.where((c) => c.name == "controls" && c.isVisible);
var barLeadingCtrls = widget.children
.where((c) => c.name == "barLeading" && c.isVisible);
var barTrailingCtrls = widget.children
.where((c) => c.name == "barTrailing" && c.isVisible);
var viewLeadingCtrls = widget.children
.where((c) => c.name == "viewLeading" && c.isVisible);
var viewTrailingCtrls = widget.children
.where((c) => c.name == "viewTrailing" && c.isVisible);

var viewBgcolor = HexColor.fromString(
Theme.of(context), widget.control.attrString("viewBgcolor", "")!);
var dividerColor = HexColor.fromString(Theme.of(context),
widget.control.attrString("dividerColor", "")!);

TextStyle? viewHeaderTextStyle = parseTextStyle(
Theme.of(context), widget.control, "viewHeaderTextStyle");
TextStyle? viewHintTextStyle = parseTextStyle(
Theme.of(context), widget.control, "viewHintTextStyle");

var method = widget.control.attrString("method");

if (method != null) {
debugPrint("SearchAnchor JSON method: $method");

var mj = json.decode(method);
var name = mj["n"] as String;
var params = Map<String, dynamic>.from(mj["p"] as Map);

if (name == "closeView") {
WidgetsBinding.instance.addPostFrameCallback((_) {
List<Map<String, String>> props = [
{"i": widget.control.id, "method": ""}
];
widget.dispatch(UpdateControlPropsAction(
UpdateControlPropsPayload(props: props)));
FletAppServices.of(context)
.server
.updateControlProps(props: props);
if (_controller.isOpen) {
var text = params["text"].toString();
setState(() {
_controller.closeView(text);
});
}
});
} else if (name == "openView") {
WidgetsBinding.instance.addPostFrameCallback((_) {
List<Map<String, String>> props = [
{"i": widget.control.id, "method": ""}
];
widget.dispatch(UpdateControlPropsAction(
UpdateControlPropsPayload(props: props)));
FletAppServices.of(context)
.server
.updateControlProps(props: props);
if (!_controller.isOpen) {
_controller.openView();
}
});
}
}

Widget anchor = SearchAnchor(
searchController: _controller,
headerHintStyle: viewHintTextStyle,
headerTextStyle: viewHeaderTextStyle,
viewSide: parseBorderSide(
Theme.of(context), widget.control, "viewSide"),
isFullScreen: widget.control.attrBool("fullScreen", false),
viewBackgroundColor: viewBgcolor,
dividerColor: dividerColor,
viewHintText: widget.control.attrString("viewHintText"),
viewElevation: widget.control.attrDouble("viewElevation"),
viewShape: parseOutlinedBorder(widget.control, "viewShape"),
viewTrailing: viewTrailingCtrls.isNotEmpty
? viewTrailingCtrls.map((ctrl) {
return createControl(widget.parent, ctrl.id, disabled);
})
: null,
viewLeading: viewLeadingCtrls.isNotEmpty
? createControl(
widget.parent, viewLeadingCtrls.first.id, disabled)
: null,
builder: (BuildContext context, SearchController controller) {
return SearchBar(
controller: controller,
hintText: widget.control.attrString("barHintText"),
backgroundColor: parseMaterialStateColor(
Theme.of(context), widget.control, "barBgcolor"),
overlayColor: parseMaterialStateColor(
Theme.of(context), widget.control, "barOverlayColor"),
leading: barLeadingCtrls.isNotEmpty
? createControl(
widget.parent, barLeadingCtrls.first.id, disabled)
: null,
trailing: barTrailingCtrls.isNotEmpty
? barTrailingCtrls.map((ctrl) {
return createControl(
widget.parent, ctrl.id, disabled);
})
: null,
onTap: () {
if (onTap) {
FletAppServices.of(context).server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "tap",
eventData: "");
}
controller.openView();
},
onSubmitted: onSubmit
? (String value) {
FletAppServices.of(context).server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "submit",
eventData: value);
}
: null,
onChanged: onChange
? (String value) {
FletAppServices.of(context).server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "change",
eventData: value);
}
: null,
);
},
suggestionsBuilder:
(BuildContext context, SearchController controller) {
return suggestionCtrls.map((ctrl) {
return createControl(widget.parent, ctrl.id, disabled);
});
});

return constrainedControl(
context, anchor, widget.parent, widget.control);
});
}
}
1 change: 1 addition & 0 deletions sdk/python/packages/flet-core/src/flet_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
from flet_core.row import Row
from flet_core.safe_area import SafeArea
from flet_core.scrollable_control import OnScrollEvent
from flet_core.search_anchor import SearchBar
from flet_core.segmented_button import Segment, SegmentedButton
from flet_core.semantics import Semantics
from flet_core.shader_mask import ShaderMask
Expand Down
Loading

0 comments on commit b1fbf87

Please sign in to comment.