Skip to content

Commit

Permalink
CupertinoTextfield control (#2417)
Browse files Browse the repository at this point in the history
* adaptive prop

* added CupertinoTextField

* reformat cupertino_textfield.py

* TextField.show_cursor

* reformat

* decoration props

* parseVisibilityMode

* export VisibilityMode
  • Loading branch information
ndonkoHenri authored Jan 17, 2024
1 parent ed672c5 commit c20e168
Show file tree
Hide file tree
Showing 8 changed files with 734 additions and 7 deletions.
8 changes: 8 additions & 0 deletions package/lib/src/controls/create_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import 'cupertino_navigation_bar.dart';
import 'cupertino_radio.dart';
import 'cupertino_slider.dart';
import 'cupertino_switch.dart';
import 'cupertino_textfield.dart';
import 'datatable.dart';
import 'date_picker.dart';
import 'dismissible.dart';
Expand Down Expand Up @@ -519,6 +520,13 @@ Widget createWidget(Key? key, ControlViewModel controlView, Control? parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled);
case "cupertinotextfield":
return CupertinoTextFieldControl(
key: key,
parent: parent,
control: controlView.control,
children: controlView.children,
parentDisabled: parentDisabled);
case "searchbar":
return SearchAnchorControl(
key: key,
Expand Down
335 changes: 335 additions & 0 deletions package/lib/src/controls/cupertino_textfield.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
import 'package:collection/collection.dart';
import 'package:flet/src/controls/textfield.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.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/colors.dart';
import '../utils/gradient.dart';
import '../utils/shadows.dart';
import '../utils/text.dart';
import '../utils/textfield.dart';
import 'create_control.dart';
import 'form_field.dart';

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

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

@override
State<CupertinoTextFieldControl> createState() =>
_CupertinoTextFieldControlState();
}

class _CupertinoTextFieldControlState extends State<CupertinoTextFieldControl> {
String _value = "";
bool _revealPassword = false;
bool _focused = false;
late TextEditingController _controller;
late final FocusNode _focusNode;
late final FocusNode _shiftEnterfocusNode;
String? _lastFocusValue;

@override
void initState() {
super.initState();
_controller = TextEditingController();
_shiftEnterfocusNode = FocusNode(
onKey: (FocusNode node, RawKeyEvent evt) {
if (!evt.isShiftPressed && evt.logicalKey.keyLabel == 'Enter') {
if (evt is RawKeyDownEvent) {
FletAppServices.of(context).server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "submit",
eventData: "");
}
return KeyEventResult.handled;
} else {
return KeyEventResult.ignored;
}
},
);
_shiftEnterfocusNode.addListener(_onShiftEnterFocusChange);
_focusNode = FocusNode();
_focusNode.addListener(_onFocusChange);
}

@override
void dispose() {
_controller.dispose();
_shiftEnterfocusNode.removeListener(_onShiftEnterFocusChange);
_shiftEnterfocusNode.dispose();
_focusNode.removeListener(_onFocusChange);
_focusNode.dispose();
super.dispose();
}

void _onShiftEnterFocusChange() {
setState(() {
_focused = _shiftEnterfocusNode.hasFocus;
});
FletAppServices.of(context).server.sendPageEvent(
eventTarget: widget.control.id,
eventName: _shiftEnterfocusNode.hasFocus ? "focus" : "blur",
eventData: "");
}

void _onFocusChange() {
setState(() {
_focused = _focusNode.hasFocus;
});
FletAppServices.of(context).server.sendPageEvent(
eventTarget: widget.control.id,
eventName: _focusNode.hasFocus ? "focus" : "blur",
eventData: "");
}

@override
Widget build(BuildContext context) {
debugPrint("CupertinoTextField build: ${widget.control.id}");

bool autofocus = widget.control.attrBool("autofocus", false)!;
bool disabled = widget.control.isDisabled || widget.parentDisabled;

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

String value = widget.control.attrs["value"] ?? "";
if (_value != value) {
_value = value;
_controller.text = value;
}

var prefixControls =
widget.children.where((c) => c.name == "prefix" && c.isVisible);
var suffixControls =
widget.children.where((c) => c.name == "suffix" && c.isVisible);

bool shiftEnter = widget.control.attrBool("shiftEnter", false)!;
bool multiline =
widget.control.attrBool("multiline", false)! || shiftEnter;
int minLines = widget.control.attrInt("minLines", 1)!;
int? maxLines =
widget.control.attrInt("maxLines", multiline ? null : 1);

bool readOnly = widget.control.attrBool("readOnly", false)!;
bool password = widget.control.attrBool("password", false)!;
bool onChange = widget.control.attrBool("onChange", false)!;

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

int? maxLength = widget.control.attrInt("maxLength");

var textSize = widget.control.attrDouble("textSize");

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

TextStyle? textStyle =
parseTextStyle(Theme.of(context), widget.control, "textStyle");
if (textSize != null || color != null || focusedColor != null) {
textStyle = (textStyle ?? const TextStyle()).copyWith(
fontSize: textSize,
color: _focused ? focusedColor ?? color : color);
}

TextCapitalization? textCapitalization = TextCapitalization.values
.firstWhere(
(a) =>
a.name.toLowerCase() ==
widget.control
.attrString("capitalization", "")!
.toLowerCase(),
orElse: () => TextCapitalization.none);

FilteringTextInputFormatter? inputFilter =
parseInputFilter(widget.control, "inputFilter");

List<TextInputFormatter>? inputFormatters = [];
// add non-null input formatters
if (inputFilter != null) {
inputFormatters.add(inputFilter);
}
if (textCapitalization != TextCapitalization.none) {
inputFormatters
.add(TextCapitalizationFormatter(textCapitalization));
}

TextInputType keyboardType = parseTextInputType(
widget.control.attrString("keyboardType", "")!);

if (multiline) {
keyboardType = TextInputType.multiline;
}

TextAlign textAlign = TextAlign.values.firstWhere(
((b) =>
b.name ==
widget.control.attrString("textAlign", "")!.toLowerCase()),
orElse: () => TextAlign.start,
);

bool autocorrect = widget.control.attrBool("autocorrect", true)!;
bool enableSuggestions =
widget.control.attrBool("enableSuggestions", true)!;
bool smartDashesType =
widget.control.attrBool("smartDashesType", true)!;
bool smartQuotesType =
widget.control.attrBool("smartQuotesType", true)!;

FocusNode focusNode = shiftEnter ? _shiftEnterfocusNode : _focusNode;

var focusValue = widget.control.attrString("focus");
if (focusValue != null && focusValue != _lastFocusValue) {
_lastFocusValue = focusValue;
focusNode.requestFocus();
}

BoxDecoration? defaultDecoration =
const CupertinoTextField().decoration;
var gradient =
parseGradient(Theme.of(context), widget.control, "gradient");
var blendMode = BlendMode.values.firstWhereOrNull((e) =>
e.name.toLowerCase() ==
widget.control.attrString("blendMode", "")!.toLowerCase());

var borderRadius = parseBorderRadius(widget.control, "borderRadius");
var bgColor = HexColor.fromString(
Theme.of(context), widget.control.attrString("bgColor", "")!);

Widget textField = CupertinoTextField(
style: textStyle,
placeholder: widget.control.attrString("placeholderText"),
placeholderStyle: parseTextStyle(
Theme.of(context), widget.control, "placeholderStyle"),
autofocus: autofocus,
enabled: !disabled,
onSubmitted: !multiline
? (_) {
FletAppServices.of(context).server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "submit",
eventData: "");
}
: null,
decoration: defaultDecoration?.copyWith(
color: bgColor,
gradient: gradient,
backgroundBlendMode:
bgColor != null || gradient != null ? blendMode : null,
border:
parseBorder(Theme.of(context), widget.control, "border"),
borderRadius: borderRadius,
boxShadow: parseBoxShadow(
Theme.of(context), widget.control, "shadow")),
cursorHeight: widget.control.attrDouble("cursorHeight"),
showCursor: widget.control.attrBool("showCursor"),
cursorWidth: widget.control.attrDouble("cursorWidth") ?? 2.0,
cursorRadius: parseRadius(widget.control, "cursorRadius") ??
const Radius.circular(2.0),
keyboardType: keyboardType,
autocorrect: autocorrect,
enableSuggestions: enableSuggestions,
smartDashesType: smartDashesType
? SmartDashesType.enabled
: SmartDashesType.disabled,
smartQuotesType: smartQuotesType
? SmartQuotesType.enabled
: SmartQuotesType.disabled,
suffixMode: parseVisibilityMode(
widget.control.attrString("suffixVisibilityMode", "")!),
prefixMode: parseVisibilityMode(
widget.control.attrString("prefixVisibilityMode", "")!),
textAlign: textAlign,
minLines: minLines,
maxLines: maxLines,
maxLength: maxLength,
prefix: prefixControls.isNotEmpty
? createControl(
widget.control, prefixControls.first.id, disabled)
: null,
suffix: suffixControls.isNotEmpty
? createControl(
widget.control, suffixControls.first.id, disabled)
: null,
readOnly: readOnly,
inputFormatters:
inputFormatters.isNotEmpty ? inputFormatters : null,
obscureText: password && !_revealPassword,
controller: _controller,
focusNode: focusNode,
onChanged: (String value) {
//debugPrint(value);
setState(() {
_value = value;
});
List<Map<String, String>> props = [
{"i": widget.control.id, "value": value}
];
dispatch(UpdateControlPropsAction(
UpdateControlPropsPayload(props: props)));
FletAppServices.of(context)
.server
.updateControlProps(props: props);
if (onChange) {
FletAppServices.of(context).server.sendPageEvent(
eventTarget: widget.control.id,
eventName: "change",
eventData: value);
}
});

if (cursorColor != null || selectionColor != null) {
textField = TextSelectionTheme(
data: TextSelectionTheme.of(context).copyWith(
cursorColor: cursorColor, selectionColor: selectionColor),
child: textField);
}

if (widget.control.attrInt("expand", 0)! > 0) {
return constrainedControl(
context, textField, widget.parent, widget.control);
} else {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth == double.infinity &&
widget.control.attrDouble("width") == null) {
textField = ConstrainedBox(
constraints: const BoxConstraints.tightFor(width: 300),
child: textField,
);
}

return constrainedControl(
context, textField, widget.parent, widget.control);
},
);
}
});
}
}
15 changes: 15 additions & 0 deletions package/lib/src/controls/form_field.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import '../models/control.dart';
Expand Down Expand Up @@ -137,3 +138,17 @@ InputDecoration buildInputDecoration(BuildContext context, Control control,
suffixText: suffixText,
suffixStyle: parseTextStyle(Theme.of(context), control, "suffixStyle"));
}

OverlayVisibilityMode parseVisibilityMode(String type) {
switch (type.toLowerCase()) {
case "never":
return OverlayVisibilityMode.never;
case "notediting":
return OverlayVisibilityMode.notEditing;
case "editing":
return OverlayVisibilityMode.editing;
case "always":
return OverlayVisibilityMode.always;
}
return OverlayVisibilityMode.always;
}
Loading

0 comments on commit c20e168

Please sign in to comment.