This is going to be way too long but I don't know where the problem is/what to do.
Updated: The values track properly if I use a StateLessWidget instead; so...
Purpose
At its core, I envisioned my TouchPoint to be a button that if tapped and dragged would pass the drag up to the scrollable container. If LongPressed, it would go into edit mode and handle a few different types of data; if OK is pressed on the dialog, it will call the parent() OnPressed function to pass the new value back out. If Cancel is pressed, then it should not call onpressed or update the label.
Problems
When the dialogbox is opened, you can't really interact with it (I have print messages on the controls and I see that it was clicked, but the UI control in the dialog doesn't reflect the "new" state).
You can't tap and drag a button
Code
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'statusword_t.dart';
enum InputType { boolean, text, numeric, outconfig, sword }
class TouchPoint extends StatelessWidget {
TouchPoint({
Key? key,
required this.title,
required this.icon,
required this.value,
required this.enabled,
required this.type,
this.onlyButtons = false,
this.warning,
this.setter,
}) : super(key: key);
final String title;
final String value;
final Icon icon;
final bool enabled;
final InputType type;
final bool onlyButtons;
final Function(String)? setter;
final String? warning;
/*@override
_TouchPointState createState() => _TouchPointState();
*/
@override
Widget build(BuildContext context) {
return ElevatedButton(
//This is the screen buton
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(
enabled ? Colors.blue : Colors.grey),
foregroundColor: MaterialStateProperty.all(Colors.white)),
child: Row(
mainAxisAlignment: MainAxisAlignment.start, //ToDo: center?
crossAxisAlignment: CrossAxisAlignment.center,
children: [
icon,
Column(
//ToDo: Alignment here doesn't seem to do anything...
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
title,
textAlign:
TextAlign.center, //ToDo: TextAlign doesn't work either...
),
_uiControl(inEdit: false, type: type, value: value),
])
],
),
onPressed: null, //ToDo: Pass click-drag to parent...
onLongPress: !enabled
? null
: () {
//ToDo: Flyout setter widget here.
print(
"Pressed: " + title + " [" + type.toString() + ": $value ]");
_inputDialog(context: context, touchPoint: this).then((retval) {
if (retval != null) {
//OK was pressed
print("Setter:" + retval);
if (setter != null) setter!(retval);
}
});
},
);
}
}
TextEditingController _textFieldController = TextEditingController();
Widget _uiControl(
{required bool inEdit,
required InputType type,
required String value,
Function(String)? onPressed,
bool? printme}) {
if (printme != null)
print("IE $inEdit, val: $value, " +
((onPressed == null) ? "nocall" : "wcall"));
switch (type) {
case InputType.numeric:
case InputType.text:
if (!inEdit)
return Text(value);
else {
Widget textf = TextField(
autofocus: true,
keyboardType: //ToDo: Flutter Bug https://github.com/flutter/flutter/issues/58510
(type == InputType.numeric)
? TextInputType.number
: TextInputType.text,
//ToDo: maxLength: maxlen,
//ToDo: Min/Max value?
controller: _textFieldController,
decoration: InputDecoration(hintText: "zzBogusHint"),
);
//Set Initial Values (since it has to be done via the Controller)
switch (type) {
case InputType.numeric:
case InputType.text:
_textFieldController.text = value;
_textFieldController.selection = TextSelection.fromPosition(
TextPosition(offset: _textFieldController.text.length));
break;
default: //These aren't keyboards
break;
}
return textf;
}
case InputType.boolean:
return SizedBox(
height: 16,
width: 16,
child: Checkbox(
value: value == "1",
onChanged: onPressed == null
? null
: (val) {
onPressed(value);
},
));
case InputType.outconfig:
return !inEdit
? ToggleButtons(
//We're gonna look like toggle buttons, but it's just a statement of what we are.
disabledColor: Colors.white,
color: Colors.white,
disabledBorderColor: Colors.white,
children: [
int.parse(value) % 2 == 0 ? Text("NO") : Text("NC"),
],
constraints: BoxConstraints(minHeight: 16, minWidth: 32),
borderRadius: BorderRadius.circular(8.0),
borderWidth: 1,
isSelected: [true])
: Row(
children: [
ToggleButtons(
children: [Text("NO"), Text("NC")],
constraints: BoxConstraints(minHeight: 16, minWidth: 32),
borderRadius: BorderRadius.circular(8.0),
borderWidth: 1,
isSelected: [
int.parse(value) % 2 == 0,
int.parse(value) % 2 == 1
],
onPressed: onPressed == null
? null
: (index) => onPressed(index.toString())),
],
);
case InputType.sword:
return StatusWordWidget(int.parse(value),
short: !inEdit, direction: !inEdit ? Axis.horizontal : Axis.vertical);
}
}
//A flyout dialog for changing values
Future<String?> _inputDialog(
{required BuildContext context, required TouchPoint touchPoint}) async {
String myval = touchPoint.value;
final dlg = showDialog<String>(
context: context,
builder: (context) {
return AlertDialog(
title: Text(touchPoint.title),
content: Column(mainAxisSize: MainAxisSize.min, //Keep it small
children: [
if (touchPoint.warning != null) Text(touchPoint.warning!),
if (!touchPoint.onlyButtons)
_uiControl(
printme: true,
inEdit: true,
type: touchPoint.type,
value: myval,
onPressed: (str) {
switch (touchPoint.type) {
case InputType.boolean:
myval = myval == "0" ? "1" : "0";
print("BoolUpdate: $myval");
break;
case InputType.outconfig:
int ival = int.parse(myval);
print("NoncB4: $str - $ival");
ival =
((ival & 0xFF00) + ((ival & 0xFF) == 0 ? 1 : 0));
myval = ival.toString();
print("NoncAfter: $ival");
break;
default:
}
},
)
]),
actions: <Widget>[
FlatButton(
color: Colors.red,
textColor: Colors.white,
child: Text('CANCEL'),
onPressed: () {
Navigator.pop(context);
},
),
FlatButton(
color: Colors.green,
textColor: Colors.white,
child: Text('OK'),
onPressed: () {
//You have to get the value of the TextField from the Controller
String retval;
switch (touchPoint.type) {
case InputType.numeric:
case InputType.text:
retval = _textFieldController.text;
break;
default:
retval = myval;
}
Navigator.pop(context, retval);
},
),
],
);
});
return dlg;
}
The real answer is that I needed to split this: The main widget is stateless - so it gets automatically updated.
The "Edit Dialog" is a separate StateFulWidget - so you can interact with it, but the values are set-on-creation.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'statusword_t.dart';
enum InputType { boolean, text, numeric, outconfig, sword }
class TouchPoint extends StatelessWidget {
TouchPoint({
Key? key,
required this.title,
required this.icon,
required this.value,
required this.enabled,
required this.type,
this.onlyButtons = false,
this.warning,
this.setter,
}) : super(key: key);
final String title;
final String value;
final Icon icon;
final bool enabled;
final InputType type;
final bool onlyButtons;
final Function(String)? setter;
final String? warning;
/*@override
_TouchPointState createState() => _TouchPointState();
*/
@override
Widget build(BuildContext context) {
return ElevatedButton(
//This is the screen buton
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(
enabled ? Colors.blue : Colors.grey),
foregroundColor: MaterialStateProperty.all(Colors.white)),
child: Row(
mainAxisAlignment: MainAxisAlignment.start, //ToDo: center?
crossAxisAlignment: CrossAxisAlignment.center,
children: [
icon,
Column(
//ToDo: Alignment here doesn't seem to do anything...
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
title,
textAlign:
TextAlign.center, //ToDo: TextAlign doesn't work either...
),
_uiControl(inEdit: false, type: type, value: value),
])
],
),
onPressed: null, //ToDo: Pass click-drag to parent...
onLongPress: !enabled
? null
: () {
//ToDo: Flyout setter widget here.
_inputDialog(context: context, touchPoint: this).then((retval) {
if (retval != null) {
//OK was pressed
if (setter != null) setter!(retval);
}
});
},
);
}
}
TextEditingController _textFieldController = TextEditingController();
Widget _uiControl(
{required bool inEdit,
required InputType type,
required String value,
Function(String)? onPressed}) {
switch (type) {
case InputType.numeric:
case InputType.text:
if (!inEdit)
return Text(value);
else {
Widget textf = TextField(
autofocus: true,
keyboardType: //ToDo: Flutter Bug https://github.com/flutter/flutter/issues/58510
(type == InputType.numeric)
? TextInputType.number
: TextInputType.text,
//ToDo: maxLength: maxlen,
//ToDo: Min/Max value?
controller: _textFieldController,
decoration: InputDecoration(hintText: "zzBogusHint"),
);
//Set Initial Values (since it has to be done via the Controller)
switch (type) {
case InputType.numeric:
case InputType.text:
_textFieldController.text = value;
_textFieldController.selection = TextSelection.fromPosition(
TextPosition(offset: _textFieldController.text.length));
break;
default: //These aren't keyboards
break;
}
return textf;
}
case InputType.boolean:
return SizedBox(
height: 16,
width: 16,
child: Checkbox(
value: value == "1",
onChanged: onPressed == null
? null
: (val) {
onPressed(value);
},
));
case InputType.outconfig:
return !inEdit
? ToggleButtons(
//We're gonna look like toggle buttons, but it's just a statement of what we are.
disabledColor: Colors.white,
color: Colors.white,
disabledBorderColor: Colors.white,
children: [
int.parse(value) % 2 == 0 ? Text("NO") : Text("NC"),
],
constraints: BoxConstraints(minHeight: 16, minWidth: 32),
borderRadius: BorderRadius.circular(8.0),
borderWidth: 1,
isSelected: [true])
: Row(
children: [
ToggleButtons(
children: [Text("NO"), Text("NC")],
constraints: BoxConstraints(minHeight: 16, minWidth: 32),
borderRadius: BorderRadius.circular(8.0),
borderWidth: 1,
isSelected: [
int.parse(value) % 2 == 0,
int.parse(value) % 2 == 1
],
onPressed: onPressed == null
? null
: (index) => onPressed(index.toString())),
],
);
case InputType.sword:
return StatusWordWidget(int.parse(value),
short: !inEdit, direction: !inEdit ? Axis.horizontal : Axis.vertical);
}
}
/* The dialog, since it changes the vaues, needs to be a stateful widget */
class _TouchPointDialog extends StatefulWidget {
//ToDo: Pass in things here
_TouchPointDialog({Key? key, required this.touchPoint}) : super(key: key);
final TouchPoint touchPoint;
@override
_TouchPointDialogState createState() =>
_TouchPointDialogState(touchPoint.value);
}
class _TouchPointDialogState extends State<_TouchPointDialog> {
_TouchPointDialogState(this.myval);
String myval;
@override
Widget build(BuildContext context) {
final dlg = AlertDialog(
title: Text(widget.touchPoint.title),
content: Column(mainAxisSize: MainAxisSize.min, //Keep it small
children: [
if (widget.touchPoint.warning != null)
Text(widget.touchPoint.warning!),
if (!widget.touchPoint.onlyButtons)
_uiControl(
inEdit: true,
type: widget.touchPoint.type,
value: myval,
onPressed: (str) {
setState(() {
switch (widget.touchPoint.type) {
case InputType.boolean:
myval = myval == "0" ? "1" : "0";
break;
case InputType.outconfig:
int ival = int.parse(myval);
ival = ((ival & 0xFF00) + ((ival & 0xFF) == 0 ? 1 : 0));
myval = ival.toString();
break;
default:
}
});
},
)
]),
actions: <Widget>[
FlatButton(
color: Colors.red,
textColor: Colors.white,
child: Text('CANCEL'),
onPressed: () {
Navigator.pop(context);
},
),
FlatButton(
color: Colors.green,
textColor: Colors.white,
child: Text('OK'),
onPressed: () {
//You have to get the value of the TextField from the Controller
String retval;
switch (widget.touchPoint.type) {
case InputType.numeric:
case InputType.text:
retval = _textFieldController.text;
break;
default:
retval = myval;
}
Navigator.pop(context, retval);
},
),
],
);
return dlg;
}
}
//A flyout dialog for changing values
Future<String?> _inputDialog(
{required BuildContext context, required TouchPoint touchPoint}) async {
return showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return _TouchPointDialog(touchPoint: touchPoint);
});
}