im trying to use an Editable text widget in Flutter and i cant select, copy, cut and paste don't work with it, i don't know why this is happening I've enabled everything that is related to selecting and copy pasting, but it still doesn't work
class EditProfile extends StatefulWidget {
#override
_EditProfileState createState() => _EditProfileState();
}
class _EditProfileState extends State<EditProfile> {
var _controller = TextEditingController();
TextSelectionControls _mainContentSelectionController;
#override
Widget _backButton() {
return InkWell(
onTap: () {
Navigator.pop(context);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Row(
children: <Widget>[
Icon(
Icons.clear,
size: 30.0,
color: Color(0xff8729e6),
)
],
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Colors.white,
child: EditableText(
controller: _controller,
toolbarOptions: ToolbarOptions(
paste: true,
copy: true,
selectAll: true,
),
readOnly : false,
focusNode: FocusNode(),
cursorColor: Colors.blue,
style: TextStyle(
color: Colors.black
),
keyboardType: TextInputType.text,
autocorrect: false,
autofocus: false,
backgroundCursorColor: Colors.red,
maxLines: 10,
minLines: 1,
enableInteractiveSelection: true,
selectionColor: Colors.red,
selectionControls: _mainContentSelectionController,
),
)
],
)
),
),
);
}
}
i hope this code is enough, please let me know if you want me to provide more code or if you have any questions, Thank you!
Try Selectable Text instead of simple text widget. more info here.
You can use TextField instead of Editable Text, this code works now.
import 'package:flutter/material.dart';
class EditProfile extends StatefulWidget {
#override
_EditProfileState createState() => _EditProfileState();
}
class _EditProfileState extends State<EditProfile> {
var _controller = TextEditingController();
TextSelectionControls _mainContentSelectionController;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: MediaQuery.of(context).size.height * 0.1,
color: Colors.white,
child: TextField(
controller: _controller,
toolbarOptions: ToolbarOptions(
paste: true,
cut: true,
copy: true,
selectAll: true,
),
readOnly: false,
focusNode: FocusNode(),
cursorColor: Colors.blue,
style: TextStyle(color: Colors.black),
keyboardType: TextInputType.text,
autocorrect: false,
autofocus: false,
maxLines: 10,
minLines: 1,
enableInteractiveSelection: true,
),
)
],
)),
),
);
}
}
As per this answer, EditableText is rarely used.
Related
I need 80 pixel top padding (so AppBar to be shown) for my bottom sheet when keyboard is visible and when keyboard is not visible.
This is my code:
import 'package:flutter/material.dart';
class Temp2Screen extends StatelessWidget {
const Temp2Screen({super.key});
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: MyWidget(),
),
);
}
}
class MyWidget extends StatefulWidget {
MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late ScrollController scrollController;
List<String> messages = [
"msg1", "msg2", "msg3", "msg4", "msg5", "msg6", "msg7", "msg8", "msg9", "msg10",
];
#override
void initState() {
super.initState();
scrollController = new ScrollController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
showModalBottomSheet(
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.vertical(top: Radius.circular(25.0))),
backgroundColor: Colors.yellow,
context: context,
isScrollControlled: true,
builder: (context) => Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: SizedBox(
height: MediaQuery.of(context).size.height - 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SingleChildScrollView(
child: Column(
children: [
for (var m in this.messages) ...[
Text(m)
]
],
),
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
hintText: 'Message',
contentPadding: EdgeInsets.all(10),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
isDense: true,
),
keyboardType: TextInputType.multiline,
maxLines: 4,
minLines: 1,
//controller: textController,
textInputAction: TextInputAction.send,
onSubmitted: (value) {
this.setState(() {
this.messages.add(value);
});
},
)
],
),
),
)
);
},
child: const Text('Show Modal Bottom Sheet'),
),
));
}
}
When keyboard is not visible everything is ok (system top panel is visible and AppBar is visible):
However, when keyboard is visible I have a problem as bottom sheet covers both top panel and and AppBar:
Could anyone say how to fix this problem so top panel and AppBar to be visible in both cases (when keyboard is on and when it is off)?
Instead of wrap your whole bottom sheet with padding, try wrap your textField with padding, like this:
builder: (context) => SizedBox(
height: MediaQuery.of(context).size.height - 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SingleChildScrollView(
child: Column(
children: [
for (var m in this.messages) ...[Text(m)]
],
),
),
Padding(
padding: EdgeInsets.only(
bottom:
MediaQuery.of(context).viewInsets.bottom),
child: TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
hintText: 'Message',
contentPadding: EdgeInsets.all(10),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
isDense: true,
),
keyboardType: TextInputType.multiline,
maxLines: 4,
minLines: 1,
//controller: textController,
textInputAction: TextInputAction.send,
onSubmitted: (value) {
this.setState(() {
this.messages.add(value);
});
},
),
)
],
),
));
I have a dialog with long content and many TextFields - at the top, middle and bottom. This is my test code
import 'package:flutter/material.dart';
class TempDialog extends StatefulWidget {
TempDialog({Key? key}) : super(key: key);
#override
State<TempDialog> createState() => _TempDialogState();
}
class _TempDialogState extends State<TempDialog> {
#override
Widget build(BuildContext context) {
var textController = TextEditingController(text: "");
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return AlertDialog(
content: Container(
width: width,
height: height,
child: Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.green,
filled: true
),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
SizedBox(height: 500,),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.red,
filled: true
),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
],
),
),
),
),
);
}
}
class TempScreen extends StatefulWidget {
TempScreen({Key? key}) : super(key: key);
#override
State<TempScreen> createState() => _TempScreenState();
}
class _TempScreenState extends State<TempScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Temp screen"),
),
body: Column(
children: [
TextButton(
onPressed: (){
showDialog(
context: context,
builder: (BuildContext context) {
return TempDialog();
}
);
},
child: Text("Tap me"))
],
),
);
}
}
And this is the result:
As you see TextField that is at the bottom is not visible on focus - scrollview doesnt scroll to its position.
Could anyone say how to fix this issue. Please, note, that solution needs to support multiple TextFields (as I've said I have many of them).
EDIT 1
I tried to use scrollable positioned list. This is my code
import 'package:flutter/material.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class TempDialog extends StatefulWidget {
TempDialog({Key? key}) : super(key: key);
#override
State<TempDialog> createState() => _TempDialogState();
}
class _TempDialogState extends State<TempDialog> {
final ItemScrollController itemScrollController = ItemScrollController();
final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create();
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Container(
width: 300,
height: 500,
child: ScrollablePositionedList.builder(
padding: EdgeInsets.only(top: 10),
itemCount: 2,
physics: ClampingScrollPhysics(),
itemBuilder: (context, index) {
return Focus(
child: Padding(
padding: EdgeInsets.only(bottom: 500),
child: TextField(
key: ValueKey("_k" + index.toString()),
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.red,
filled: true
),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
//controller: textController,
),
),
onFocusChange: (hasFocus) {
if (hasFocus) {
// itemScrollController.jumpTo(index: index);
itemScrollController.scrollTo(
index: index,
duration: Duration(seconds: 2),
curve: Curves.easeInOutCubic);
}
} ,
);
},
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
),
),
);
}
}
class TempScreen extends StatefulWidget {
TempScreen({Key? key}) : super(key: key);
#override
State<TempScreen> createState() => _TempScreenState();
}
class _TempScreenState extends State<TempScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Temp screen"),
),
body: Column(
children: [
TextButton(
onPressed: (){
showDialog(
context: context,
builder: (BuildContext context) {
return TempDialog();
}
);
},
child: Text("Tap me"))
],
),
);
}
}
and this is the result:
As you see the problem is that when keyboard is shown it doesn't scroll to focused item.
Your code works absolutely fine on both devices. I have added a gesture detector to the Container since the textfields were multi-lined so there was no option to lower the keyboard in iOS. Here is the code that I have used
import 'package:flutter/material.dart';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:image/image.dart' as img;
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(home: TempScreen());
}
}
class TempDialog extends StatefulWidget {
TempDialog({Key? key}) : super(key: key);
#override
State<TempDialog> createState() => _TempDialogState();
}
class _TempDialogState extends State<TempDialog> {
#override
Widget build(BuildContext context) {
var textController = TextEditingController(text: "");
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return AlertDialog(
content: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
width: width,
height: height,
child: Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.purple,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
SizedBox(
height: 90,
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.green,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
SizedBox(
height: 90,
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.blue,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
SizedBox(
height: 90,
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.red,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
SizedBox(
height: 90,
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.yellow,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
SizedBox(
height: 90,
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.red,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
),
],
),
),
),
),
),
);
}
}
class TempScreen extends StatefulWidget {
TempScreen({Key? key}) : super(key: key);
#override
State<TempScreen> createState() => _TempScreenState();
}
class _TempScreenState extends State<TempScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Temp screen"),
),
body: Column(
children: [
TextButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return TempDialog();
});
},
child: Text("Tap me"))
],
),
);
}
}
EDIT
You can create a key and assign it to the textfield and you can use this key and scroll to that widget's position like this.
final dataKey = GlobalKey();
TextField(
key: dataKey,
textAlign: TextAlign.left,
decoration: const InputDecoration(
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.red,
filled: true),
keyboardType: TextInputType.multiline,
maxLines: null,
minLines: 2,
controller: textController,
onTap: () {
Scrollable.ensureVisible(dataKey.currentContext!);//here you can scroll to the respective widget referring the key.
},
),
Please note that if you have a lot of textfields, this may result in some performance issue.. But scrolling to the right widget will work.
I managed to achieve that (not by myself, but I can't recall where I found help) in a similar case.
My widget structure is: AlertDialog -> Container (for width setting) -> StatefulBuilder. The last one, after a bit of preparation, returns this:
return Form(
key: _formKey,
child: ScrollablePositionedList.builder(
padding: EdgeInsets.only(top: 10),
itemCount: childrenList.length,
itemBuilder: (context, index) => childrenList[index],
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
));
where
childrenList contains the list of widgets, most of them being TextFormField
itemBuilder just takes the items in order
itemScrollController is just final ItemScrollController itemScrollController = ItemScrollController();
itemPositionsListener is just final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create();
I don't think that there's anything else involved in the "auto-scrolling" thing, but I wrote this a while ago, so let me know if something's missing.
What I am aiming for is, when a user long presses on a particular date, they should be able to enter their weight value, and upon submitting, the modal bottom sheet should close, and the updated weight should be visible as soon as the modal sheet is out of view. But It does not happen.
Instead I have to go to other date and come back to see the changes. Please help me. I am new to flutter.
Here is the demo of the problem :
problem i am facing
Here is the code:
void _modalBottomSheetMenu(DateTime dt) {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
context: context,
isScrollControlled: true,
builder: (builder) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text("Add a weight"),
Container(
width: 200.0,
child: TextField(
controller: myController,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
autofocus: true,
maxLength: 4,
onSubmitted: (value) {
weightValue = double.parse(value);
print("Weight entered is $weightValue");
print("Date passed is $dt");
setState(() {
_events[dt] = [weightValue].toList();
Navigator.pop(context);
});
print(_events[dt][0]);
},
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Weight',
hintText: 'Enter your weight'),
),
),
],
);
},
);
}
BuildEventList : This contains the weight to be printed
import 'package:flutter/material.dart';
class BuildEventList extends StatefulWidget {
String weight;
BuildEventList(this.weight);
#override
_BuildEventListState createState() => _BuildEventListState();
}
class _BuildEventListState extends State<BuildEventList> {
#override
void setState(fn) {
// TODO: implement setState
super.setState(fn);
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border.all(width: 0.8),
borderRadius: BorderRadius.circular(12.0),
),
margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
child: Container(
width: double.infinity,
child: Center(child: Text("${widget.weight}")),
),
);
}
}
This is where it is called :
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title), actions: [
IconButton(
icon: Icon(Icons.add),
color: Colors.white,
onPressed: () {},
)
]),
body: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
_buildTableCalendar(),
const SizedBox(height: 8.0),
//_buildButtons(),
const SizedBox(height: 8.0),
Expanded(
child: BuildEventList(_selectedEvents.length > 0
? _selectedEvents[0].toString()
: "No weight given!")),
],
),
);
}
I have a flutter web app (which may become a desktop app in the future) that contains a dialog box. The dialog allows the user to edit the properties of a model object that contains a list of elements. The user should be able to edit the list by adding or deleting elements to the list. So the dialog contents are of variable height -- it depends on the number of elements in the list.
I am having trouble creating a layout that dynamically resizes appropriately. What I want is for the dialog to grow as items are added to the list, up to the maximum size that would fit on the device's screen. If the contents grow larger than this, the elements in the list should be scrollable.
I've attached two screenshots of what I have working at the moment; the first has a list with only two items, which is easily displayed. The second is the same dialog with many items showing the overflow.
Here is the code for the Dialog:
Widget build(BuildContext context) {
return AlertDialog(
actions: [
FlatButton(
child: const Text('CANCEL'),
onPressed: () => Navigator.of(context).pop(),
),
FlatButton(
child: const Text('OK'),
onPressed: () =>
Navigator.of(context).pop<Modifier>(_createModifier()),
),
],
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Damage Editor'),
Divider(),
columnSpacer,
DiceSpinner(
onChanged: (value) => setState(() => _dice = value),
initialValue: _dice,
textFieldWidth: 90.0,
),
columnSpacer,
Container(
padding: const EdgeInsets.only(left: 12.0, right: 12.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.0),
border: Border.all(color: Colors.grey),
),
child: DropdownButton<DamageType>(
underline: Container(),
value: _type,
items: _damageTypeItems(),
onChanged: (value) => setState(() => _type = value),
),
),
columnSpacer,
SwitchListTile(
value: _direct,
onChanged: (state) => setState(() => _direct = state),
title: Text(_direct ? 'Internal (Direct)' : 'External (Indirect)'),
),
if (!_direct) ...<Widget>[
columnSpacer,
CheckboxListTile(
value: _explosive,
onChanged: (state) => setState(() => _explosive = state),
title: Text('Explosive'),
),
],
columnSpacer,
DynamicListHeader(
title: 'Enhancements/Limitations',
onPressed: () => setState(() =>
_modifiers.add(TraitModifier(name: 'Undefined', percent: 0))),
),
SingleChildScrollView(
child: ListBody(
children: _enhancementList(),
),
),
],
),
);
}
List<Widget> _enhancementList() {
var list = <Widget>[];
_modifiers.forEach(
(element) {
if (_modifiers.length > 0) list.add(columnSpacer);
list.add(_EnhancerEditor(element, index: _modifiers.indexOf(element),
onChanged: (index, enhancer) {
_modifiers[index] = enhancer;
}));
},
);
return list;
}
typedef TraitModifierCallback = void Function(int, TraitModifier);
class _EnhancerEditor extends StatefulWidget {
_EnhancerEditor(this.enhancer, {this.onChanged, this.index});
final TraitModifier enhancer;
final TraitModifierCallback onChanged;
final int index;
#override
__EnhancerEditorState createState() => __EnhancerEditorState();
}
class __EnhancerEditorState extends State<_EnhancerEditor> {
TextEditingController _nameController;
TextEditingController _percentController;
bool _validInput = true;
#override
void initState() {
super.initState();
_nameController = TextEditingController(text: widget.enhancer.name);
_nameController.addListener(_onChanged);
_percentController =
TextEditingController(text: widget.enhancer.percent.toString());
_percentController.addListener(_onChanged);
}
#override
void dispose() {
_nameController.removeListener(_onChanged);
_nameController.dispose();
_percentController.removeListener(_onChanged);
_percentController.dispose();
super.dispose();
}
void _onChanged() {
String text = _percentController.text.trim();
setState(() {
int value = int.tryParse(text);
_validInput = (value != null);
if (_validInput) {
widget.onChanged(widget.index,
TraitModifier(name: _nameController.text, percent: value));
}
});
}
#override
Widget build(BuildContext context) {
return IntrinsicHeight(
child: Row(
children: [
Expanded(
child: TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Enhancer/Limitation',
border: const OutlineInputBorder(),
),
),
),
rowSmallSpacer,
SizedBox(
width: 80.0,
child: TextField(
controller: _percentController,
textAlign: TextAlign.end,
keyboardType: TextInputType.numberWithOptions(signed: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9\-]'))
],
decoration: const InputDecoration(
suffixText: '%',
labelText: 'Pct',
border: const OutlineInputBorder(),
),
),
),
],
),
);
}
}
It may sound counterintuitive but you should have ListView within Column. You need both.
In dialog Column will deal with dialog size, and ListView will deal with scrolling within.
Column(
mainAxisSize: MainAxisSize.min,
children:[
ListView(padding: EdgeInsets.all(0),
shrinkWrap: true,
physics: ClampingScrollPhysics(),
Ok, here you go ... Simple call of dialog ...
FlatButton(
onPressed: () {
showDialog(
useRootNavigator: false,
context: context,
builder: (BuildContext context) => Test(),
);
},
child: Text('Suppper'),
)
then code of dialog ... it is pretty straight forward ...
all controllers you can place in a map that you will deal with when you add new entries... both adding and removing is super easy ... let me know if further assitance is needed.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
#override
void initState() {
super.initState();
}
Widget rowEntry(_nameController, _percentController) {
return IntrinsicHeight(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: TextField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Enhancer/Limitation',
border: const OutlineInputBorder(),
),
),
),
SizedBox(
width: 80.0,
child: TextField(
controller: _percentController,
textAlign: TextAlign.end,
keyboardType: TextInputType.numberWithOptions(signed: true),
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[0-9\-]'))],
decoration: const InputDecoration(
suffixText: '%',
labelText: 'Pct',
border: const OutlineInputBorder(),
),
),
),
],
),
);
}
addEntry() async {
entries.add('new value');
setState(() {});
}
List entries = [];
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
child: Container(
padding: EdgeInsets.all(16),
width: 300,
child: ListView(shrinkWrap: true, children: [
Text('here you will put all that is above (+)'),
Container(
margin: EdgeInsets.only(top: 16, bottom: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Exhancements/Limitations'),
InkWell(
onTap: () {
addEntry();
},
child: Icon(Icons.add),
),
],
),
),
ListView.builder(
physics: ClampingScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsets.all(0),
scrollDirection: Axis.vertical,
itemCount: entries == null ? 0 : (entries.length),
itemBuilder: (BuildContext context, int index) {
return rowEntry(null, null);
}),
]),
),
);
}
}
My task was to add button Done to text keyboard which hides keyboard after CupertinoTextField is filled with value. I used keyboard_actions lib and it worked fine, but the area of FormKeyboardActions() widget became unscrollable - scrolling is broken after my implementation of
keyboard_actions lib.
I tried to add directly CupertinoTextField to FormKeyboardActions() as a child, but it is also not working.
The code of parent PageTemplate widget:
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: Scaffold(
body: FormKeyboardActions(
child: SafeArea( //child
child: Padding(
padding: DesignPadding.V16__X,
child: ListView(
shrinkWrap: true,
children: this.children
),
),
),
),
floatingActionButton: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 8),
child: this.floatingActionButton,
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
),
);
}
The widget with CupertinoTextField:
class _ProductEditCommentState extends State<ProductEditComment> {
final FocusNode focusNode = FocusNode(skipTraversal: true);
KeyboardActionsConfig _buildConfig(BuildContext context) {
return KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.ALL,
keyboardBarColor: Colors.grey[200],
nextFocus: false,
actions: [
KeyboardAction(
focusNode: focusNode,
closeWidget: Padding(
padding: DesignPadding.V8__X,
child: Text(
"Done",
style: TextStyle(
color: DesignColors.BLACK,
fontSize: DesignFonts.FONT_SIZE__16,
fontFamily: DesignFonts.FONT_FAMILY__MEDIUM,
),
),
),
),
],
);
}
#override
void initState() {
FormKeyboardActions.setKeyboardActions(context, _buildConfig(context));
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
ProductEditBloc bloc = BlocProvider.of(context);
return SingleChildScrollView(
child: CupertinoTextField(
focusNode: focusNode,
maxLines: null,
onChanged: this.widget.onChanged,
controller: this.widget.controller,
keyboardType: TextInputType.multiline,
decoration: BoxDecoration(border: null),
textCapitalization: TextCapitalization.sentences,
placeholder: bloc.productComment == null || bloc.productComment.isEmpty
? "Добавить заметку"
: bloc.productComment,
placeholderStyle: TextStyle(
color: DesignColors.GREY_CHROME,
fontSize: DesignFonts.FONT_SIZE__14,
fontFamily: DesignFonts.FONT_FAMILY__REGULAR),
style: TextStyle(
color: DesignColors.GREY_COAL,
fontSize: DesignFonts.FONT_SIZE__14,
fontFamily: DesignFonts.FONT_FAMILY__REGULAR),
padding: EdgeInsets.only(
left: DesignPadding.V10,
top: DesignPadding.V14,
bottom: DesignPadding.V20,
),
),
);
}
}
The implementation of ProductEditComment widget:
#override
Widget build(BuildContext context) {
return BlocProvider(
bloc: bloc,
child: PageTemplate(
children: <Widget>[
Heading(
title: "Заметка",
style: DesignFonts.STYLE_TITLE,
),
StreamBuilder<String>(
stream: bloc.outComment,
initialData: this.widget.product?.comment,
builder: (context, snapshot) {
return ProductEditComment(
name: snapshot.data,
onChanged: bloc.setComment,
controller: bloc.commentController,
);
}),
]
)