Flutter: How to insert text in middle of text field text - flutter

I have a text field in flutter and an emoji picker button. On selecting an emoji I need to insert it at the current cursor position.
How can I achieve this?
Currently using TextEditingController I'm only able to append the emoji. I'm not able to get the current cursor offset.
emojiPicker() {
return EmojiPicker(
rows: 3,
columns: 7,
recommendKeywords: null,
indicatorColor: flujoAccentColor,
onEmojiSelected: (emoji, category) {
print(emoji);
_messageInputController.text =
_messageInputController.text + emoji.emoji;
}
);
}

Use _txtController.selection to get the selection (or cursor position).
replace the selection with selected emoji.
then fix the selection(or cursor position) of the controller
import 'package:emoji_picker/emoji_picker.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: HomePage()));
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
TextEditingController _messageInputController;
#override
void initState() {
_messageInputController = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: SafeArea(
child: Column(
children: <Widget>[
EmojiPicker(
rows: 3,
columns: 7,
recommendKeywords: null,
indicatorColor: Colors.red,
onEmojiSelected: (emoji, category) {
String text = _messageInputController.text;
TextSelection textSelection = _messageInputController.selection;
String newText = text.replaceRange(
textSelection.start, textSelection.end, emoji.emoji);
final emojiLength = emoji.emoji.length;
_messageInputController.text = newText;
_messageInputController.selection = textSelection.copyWith(
baseOffset: textSelection.start + emojiLength,
extentOffset: textSelection.start + emojiLength,
);
},
),
TextField(
controller: _messageInputController,
),
],
),
),
);
}
}
with this you can not only insert the selected emoji at cursor position, but also can replace some selected text

This is a slight modification to CrazyLazyCat's answer.
void _insertText(String inserted) {
final text = _controller.text;
final selection = _controller.selection;
final newText = text.replaceRange(selection.start, selection.end, inserted);
_controller.value = TextEditingValue(
text: newText,
selection: TextSelection.collapsed(offset: selection.baseOffset + inserted.length),
);
}
Notes:
_controller is a TextEditingController.
If you are changing both the text and the selection then you should use a TextEditingValue rather than changing them individually (since they each trigger an update).
Generally if you insert something you want the cursor to appear after then insert, thus the TextSelection.collapsed with the adjusted index.

i have another solution beside text.replaceRange.
All you need is :
TextEditingController _tec;
String youWillAddtoTEC = "your emoji or your clipboard data or else";
String beforeCursorPositionAtTEC = tec.selection.textBefore(tec.text);
String afterCursorPositionAtTEC = tec.selection.textAfter(tec.text);
String result = beforeCursorPositionAtTEC + youWillAddtoTEC + afterCursorPositionAtTEC;
and then add result to tec, or any widget your need:
tec.text = result;
for the selection or cursor position is same with above, but if you need place cursor after the "youWillAddToTEC" you can do like this:
tec.selection = TextSelection.collapsed(offset: tec.selection.start + youWillAddtoTEC.lenght);

If you want to replace the selection of text field with a new string, I found the method below is useful.
void replaceTextSelectionWith(TextEditingController textEditingController, Function(String selection) getReplaceString)
{
final text = textEditingController.text;
final selection = textEditingController.selection;
final replaceText = getReplaceString(selection.textInside(text)) as String;
final newText = text.replaceRange(selection.start, selection.end, replaceText);
textEditingController.value =
TextEditingValue(text: newText, selection: TextSelection.collapsed(offset: selection.start + replaceText.length));
}
And use it like this
replaceTextSelectionWith(textEditingController, (selection) => '**$selection**');

Related

About securing password field in textformfield in flutter

I want to hide completely the password field, I set obscure text true but it shows the characters as I type them how hide completely the characters?
If you want completely hide it, you must use controller and implement like this:
class TextFieldPassWord extends StatefulWidget {
#override
_TextFieldPassWordState createState() => _TextFieldPassWordState();
}
class _TextFieldPassWordState extends State<TextFieldPassWord> {
String _valueShow = "";
String _value = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: TextField(
controller: TextEditingController.fromValue(
TextEditingValue(
text: _valueShow,
selection: TextSelection.collapsed(offset: _valueShow.length),
),
),
onChanged: (String str) {
String value = "";
if (str.length > _value.length) {
value += str.substring(_value.length, str.length);
}
if (str.length < _value.length) {
value = _value.substring(1, str.length);
}
String valueToShow = "*" * str.length;
setState(() {
_valueShow = valueToShow;
_value = value;
});
},
),
);
}
}

flutter I want to convert NumberFormat form to double

Below is the code where the error occurs.
I want to convert [NumberFormat][1] form to double. What should I do?
I want to display it on the screen with the currency symbol, and get the result as a double .
Sample source:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class ProductEditor extends StatefulWidget {
const ProductEditor({Key? key}) : super(key: key);
#override
_ProductEditorState createState() => _ProductEditorState();
}
class _ProductEditorState extends State<ProductEditor> {
final NumberFormat euNumFormat =
NumberFormat.currency(locale: 'eu_EU', symbol: '€');
TextEditingController _ctrPrice = TextEditingController();
double _price = 25000.00;
#override
void initState() {
super.initState();
_ctrPrice.text = euNumFormat.format(_price); //
}
#override
Widget build(BuildContext context) {
return Container(
width: 500,
height: 300,
child: TextField(
controller: _ctrPrice,
onSubmitted: (String price) {
setState(() {
final double value = double.tryParse(_ctrPrice.text
.substring(0, _ctrPrice.text.length - 2)
.replaceAll(',', '')) ??
0.0;
print('price:${_ctrPrice.value.toString()} ${value}');
final formattedPrice = euNumFormat.format(value);
_ctrPrice.value = TextEditingValue(
text: formattedPrice,
selection: TextSelection.collapsed(offset: formattedPrice.length),
);
});
},
),
);
}
}
You can create a substring by removing symbol and , from there, then parse it.
final double retailPrice = double.tryParse(
_ctrPrice.substring(0, _ctrPrice.length - 2).replaceAll(",", ''),
) ??
0.0;
This solution only work on specific number format('eu_EU').

Flutter Widget that shows text line by line?

Is there a widget in Flutter that allows children Text widgets to be shown line by line at every press of the widget? This should act similar to how bulleted lines in a powerpoint presentation act after every click.
I just tested and it works, probably I did not cover some edge cases, but it is not a lot of work...
//I would call it TIM like VIM :P
class TextIMproved extends StatefulWidget {
final String _longString;
final int _numberOfWordsPerRow;
TextIMproved(this._numberOfWordsPerRow, this._longString);
#override
_TextIMprovedState createState() => _TextIMprovedState(_numberOfWordsPerRow, _longString);
}
class _TextIMprovedState extends State<TextIMproved> {
final String longString;
List<String> listString;
int _numberOfWordsPerRow;
String strPopulated;
String strToDisplay='';
_TextIMprovedState(this._numberOfWordsPerRow, this.longString);
#override
void initState() {
super.initState();
listString = longString.split(' ');
splitString();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(
child: Text(strToDisplay, maxLines: longString.length ~/ _numberOfWordsPerRow + _numberOfWordsPerRow,),
),
onTap: splitString,
);
}
void splitString() {
//1.prepare empty string
strPopulated = '';
//2.populate string
int len = listString.length <_numberOfWordsPerRow ? listString.length : _numberOfWordsPerRow;
for (int i = 0; i < len; i++) {
strPopulated += listString[i] + ' ';
}
//3. display portion of string
setState(() {
strToDisplay += strPopulated;
});
//4.remove displayed text
List<String> listToremove = strPopulated.split(' ');
for (String str in listToremove) {
listString.remove(str);
}
}
}
EDIT:
you can add animation to this widget...

Get text before/after cursor, on currently edited line, in TextField

I can print out all text before and after the cursor using this code:
controller = TextEditingController();
final selection = controller.value.selection;
final text = controller.value.text;
print("\nBEFORE\n${selection.textBefore(text)}");
print("\nAFTER\n${selection.textAfter(text)}\n");
How can I use the TextEditingController to find only the text before/after the cursor on the same line as the cursor?
For example, I want to use the TextEditingController to query the text before/after the current position of the cursor so the text before is "lin" and the text after the cursor is "e2 contents" (in the image above).
Full app code that gets all text before/after the cursor (not on the same line as required):
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: TextFieldApp()));
class TextFieldApp extends StatefulWidget {
#override
_TextFieldAppState createState() => _TextFieldAppState();
}
class _TextFieldAppState extends State<TextFieldApp> {
TextEditingController controller;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: TextField(
keyboardType: TextInputType.multiline,
controller: controller,
maxLines: 10,
maxLength: null,
),
);
}
#override
void dispose() {
super.dispose();
}
#override
void initState() {
controller = TextEditingController();
controller.text = 'line 1\nline2 contents\nline3';
controller.addListener(() {
final selection = controller.value.selection;
final text = controller.value.text;
print("\nBEFORE\n${selection.textBefore(text)}");
print("\nAFTER\n${selection.textAfter(text)}\n");
});
super.initState();
}
}
You can split the text by a \n then take the last value from the array.
Similarly the first entry for the text after.
final selection = controller.value.selection;
final text = controller.value.text;
final before = selection.textBefore(text);
final after = selection.textAfter(text);
print(before.split('\n').last);
print(after.split('\n').first);
Note:
If the users selects cont in a line with content line2 contents they would be
line2 //before
ents //after
If you need the text 'after' to also have the 'selected text',
you may add the text 'inside' to the text 'after'.
selection.textInside(text) + after.split('\n').first;

How to change color of particular text in a text field dynamically?

Consider bellow image, I want to dynamically change the text color of part of the text based on the user input text (not the whole text) in a text field. How can i do that in flutter?
For this example we actually don't need a full blown rich-text editor.
I had a similar goal in my app to highlight tags (#flutter) or date references (next week, on Friday, etc) and I was able to implement this by extending built-in EditableText widget and posted my example as a Gist here: https://gist.github.com/pulyaevskiy/d7af7217c2e71f31dfb78699f91dfbb5
Below is full implementation of this widget which I called AnnotatedEditableText.
There is new property annotations which describes ranges of text that need to be highlighted and their style.
import 'package:flutter/widgets.dart';
class Annotation extends Comparable<Annotation> {
Annotation({#required this.range, this.style});
final TextRange range;
final TextStyle style;
#override
int compareTo(Annotation other) {
return range.start.compareTo(other.range.start);
}
#override
String toString() {
return 'Annotation(range:$range, style:$style)';
}
}
class AnnotatedEditableText extends EditableText {
AnnotatedEditableText({
Key key,
FocusNode focusNode,
TextEditingController controller,
TextStyle style,
ValueChanged<String> onChanged,
ValueChanged<String> onSubmitted,
Color cursorColor,
Color selectionColor,
TextSelectionControls selectionControls,
this.annotations,
}) : super(
key: key,
focusNode: focusNode,
controller: controller,
cursorColor: cursorColor,
style: style,
keyboardType: TextInputType.text,
autocorrect: true,
autofocus: true,
selectionColor: selectionColor,
selectionControls: selectionControls,
onChanged: onChanged,
onSubmitted: onSubmitted,
);
final List<Annotation> annotations;
#override
AnnotatedEditableTextState createState() => new AnnotatedEditableTextState();
}
class AnnotatedEditableTextState extends EditableTextState {
#override
AnnotatedEditableText get widget => super.widget;
List<Annotation> getRanges() {
var source = widget.annotations;
source.sort();
var result = new List<Annotation>();
Annotation prev;
for (var item in source) {
if (prev == null) {
// First item, check if we need one before it.
if (item.range.start > 0) {
result.add(new Annotation(
range: TextRange(start: 0, end: item.range.start),
));
}
result.add(item);
prev = item;
continue;
} else {
// Consequent item, check if there is a gap between.
if (prev.range.end > item.range.start) {
// Invalid ranges
throw new StateError(
'Invalid (intersecting) ranges for annotated field');
} else if (prev.range.end < item.range.start) {
result.add(Annotation(
range: TextRange(start: prev.range.end, end: item.range.start),
));
}
// Also add current annotation
result.add(item);
prev = item;
}
}
// Also check for trailing range
final String text = textEditingValue.text;
if (result.last.range.end < text.length) {
result.add(Annotation(
range: TextRange(start: result.last.range.end, end: text.length),
));
}
return result;
}
#override
TextSpan buildTextSpan() {
final String text = textEditingValue.text;
if (widget.annotations != null) {
var items = getRanges();
var children = <TextSpan>[];
for (var item in items) {
children.add(
TextSpan(style: item.style, text: item.range.textInside(text)),
);
}
return new TextSpan(style: widget.style, children: children);
}
return new TextSpan(style: widget.style, text: text);
}
}
Rich text controller works fine!
See more on https://pub.dev/packages/rich_text_controller
First you choose your RegExp
RichTextController _controller;
Map<RegExp, TextStyle> patternUser = {
RegExp(r"\B#[a-zA-Z0-9]+\b"):
TextStyle(color: Colors.amber, fontWeight: FontWeight.bold)
};
on initState()
_controller = RichTextController(
patternMap: patternUser,
);
Add controller on your TextFormField
TextFormField(
controller: _controller,
style: TextStyle(color: Colors.white),
)