ChecklistEditor: Improve focus handling

It's still a bit of a mess. I seem to be hitting this bug -
https://github.com/flutter/flutter/issues/20706 which others are also
hitting, but it seems to be closed despite a clearly reproduce test
case.
This commit is contained in:
Vishesh Handa
2020-05-02 02:28:30 +02:00
parent 441202af48
commit 793660e42f
2 changed files with 43 additions and 13 deletions

View File

@ -158,6 +158,7 @@ class Checklist {
var prevItem = items.last; var prevItem = items.last;
item.lineNo = prevItem.lineNo + 1; item.lineNo = prevItem.lineNo + 1;
items.add(item);
_lines.insert(item.lineNo, item.toString()); _lines.insert(item.lineNo, item.toString());
} }
@ -183,6 +184,7 @@ class Checklist {
if (index == items.length) { if (index == items.length) {
var prevItem = items.last; var prevItem = items.last;
item.lineNo = prevItem.lineNo + 1; item.lineNo = prevItem.lineNo + 1;
items.add(item);
_lines.insert(item.lineNo, item.toString()); _lines.insert(item.lineNo, item.toString());
return; return;
} }

View File

@ -49,7 +49,9 @@ class ChecklistEditor extends StatefulWidget implements Editor {
class ChecklistEditorState extends State<ChecklistEditor> class ChecklistEditorState extends State<ChecklistEditor>
implements EditorState { implements EditorState {
Checklist checklist; Checklist checklist;
var focusNodes = <ChecklistItem, FocusScopeNode>{}; var focusNodes = <UniqueKey, FocusScopeNode>{};
var keys = <UniqueKey, ChecklistItem>{};
TextEditingController _titleTextController = TextEditingController(); TextEditingController _titleTextController = TextEditingController();
bool _noteModified; bool _noteModified;
@ -67,9 +69,8 @@ class ChecklistEditorState extends State<ChecklistEditor>
var item = checklist.buildItem(false, ""); var item = checklist.buildItem(false, "");
checklist.addItem(item); checklist.addItem(item);
} }
for (var item in checklist.items) { for (var item in checklist.items) {
focusNodes[item] = FocusScopeNode(); keys[UniqueKey()] = item;
} }
} }
@ -79,6 +80,29 @@ class ChecklistEditorState extends State<ChecklistEditor>
super.dispose(); super.dispose();
} }
UniqueKey _getKey(ChecklistItem item) {
for (var e in keys.entries) {
if (e.value == item) {
return e.key;
}
}
var key = UniqueKey();
keys[key] = item;
return key;
}
FocusScopeNode _getFn(ChecklistItem item) {
var key = _getKey(item);
var fn = focusNodes[key];
if (fn == null) {
fn = FocusScopeNode();
focusNodes[key] = fn;
}
return fn;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var itemTiles = <Widget>[]; var itemTiles = <Widget>[];
@ -92,13 +116,14 @@ class ChecklistEditorState extends State<ChecklistEditor>
onPressed: () { onPressed: () {
_noteTextChanged(); _noteTextChanged();
setState(() { setState(() {
var fn = FocusScopeNode();
var item = checklist.buildItem(false, ""); var item = checklist.buildItem(false, "");
var fn = _getFn(item);
checklist.addItem(item); checklist.addItem(item);
focusNodes[item] = fn;
// FIXME: Make this happen on the next build // FIXME: Make this happen on the next build
Timer(const Duration(milliseconds: 50), () { Timer(const Duration(milliseconds: 50), () {
FocusScope.of(context).requestFocus();
FocusScope.of(context).requestFocus(fn); FocusScope.of(context).requestFocus(fn);
}); });
}); });
@ -156,7 +181,7 @@ class ChecklistEditorState extends State<ChecklistEditor>
return ChecklistItemTile( return ChecklistItemTile(
key: UniqueKey(), key: UniqueKey(),
item: item, item: item,
focusNode: focusNodes[item], focusNode: _getFn(item),
isNewNote: autofocus, isNewNote: autofocus,
statusChanged: (bool newVal) { statusChanged: (bool newVal) {
setState(() { setState(() {
@ -181,16 +206,19 @@ class ChecklistEditorState extends State<ChecklistEditor>
FocusNode fn; FocusNode fn;
if (nextIndex >= 0) { if (nextIndex >= 0) {
var nextItemForFocus = checklist.items[nextIndex]; var nextItemForFocus = checklist.items[nextIndex];
fn = focusNodes[nextItemForFocus]; fn = _getFn(nextItemForFocus);
print("Giving focus to $nextItemForFocus"); print("Giving focus to $nextItemForFocus");
} }
focusNodes.remove(item); var k = _getKey(item);
focusNodes.remove(k);
keys.remove(k);
checklist.removeItem(item); checklist.removeItem(item);
// FIXME: Make this happen on the next build // FIXME: Make this happen on the next build
Timer(const Duration(milliseconds: 300), () { Timer(const Duration(milliseconds: 200), () {
if (fn != null) { if (fn != null) {
FocusScope.of(context).requestFocus();
FocusScope.of(context).requestFocus(fn); FocusScope.of(context).requestFocus(fn);
} }
}); });
@ -199,14 +227,14 @@ class ChecklistEditorState extends State<ChecklistEditor>
itemFinished: () { itemFinished: () {
_noteTextChanged(); _noteTextChanged();
setState(() { setState(() {
var fn = FocusScopeNode();
var item = checklist.buildItem(false, ""); var item = checklist.buildItem(false, "");
var fn = _getFn(item);
checklist.insertItem(index + 1, item); checklist.insertItem(index + 1, item);
focusNodes[item] = fn;
// FIXME: Make this happen on the next build // FIXME: Make this happen on the next build
Timer(const Duration(milliseconds: 50), () { Timer(const Duration(milliseconds: 50), () {
print("Asking focus to ${index + 1}"); print("Asking focus to ${index + 1}");
FocusScope.of(context).requestFocus();
FocusScope.of(context).requestFocus(fn); FocusScope.of(context).requestFocus(fn);
}); });
}); });
@ -252,6 +280,7 @@ class _ChecklistItemTileState extends State<ChecklistItemTile> {
_textController.addListener(() { _textController.addListener(() {
widget.textChanged(_textController.value.text); widget.textChanged(_textController.value.text);
}); });
assert(widget.focusNode != null);
widget.focusNode.addListener(_onFocus); widget.focusNode.addListener(_onFocus);
} }
@ -264,8 +293,7 @@ class _ChecklistItemTileState extends State<ChecklistItemTile> {
} }
void _onFocus() { void _onFocus() {
setState(() {}); // rebuild to hide close button setState(() {}); // rebuild to show/hide close button
SystemChannels.textInput.invokeMethod('TextInput.show');
} }
@override @override