Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: kanban board card text input inconsistency #5307

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,33 @@ class _TextCellState extends State<TextCardCell> {
widget.cellContext,
).as(),
);
late final TextEditingController _textEditingController =
TextEditingController(text: cellBloc.state.content);
late final TextEditingController _textEditingController;
final focusNode = SingleListenerFocusNode();

bool focusWhenInit = false;

@override
void initState() {
super.initState();
focusWhenInit = widget.editableNotifier?.isCellEditing.value ?? false;
if (focusWhenInit) {
focusNode.requestFocus();

_textEditingController = TextEditingController(text: cellBloc.state.content)
..addListener(() {
if (_textEditingController.value.composing.isCollapsed) {
cellBloc
.add(TextCellEvent.updateText(_textEditingController.value.text));
}
});

if (widget.editableNotifier?.isCellEditing.value ?? false) {
WidgetsBinding.instance.addPostFrameCallback((_) {
focusNode.requestFocus();
cellBloc.add(const TextCellEvent.enableEdit(true));
});
}

// If the focusNode lost its focus, the widget's editableNotifier will
// set to false, which will cause the [EditableRowNotifier] to receive
// end edit event.
focusNode.addListener(() {
if (!focusNode.hasFocus) {
focusWhenInit = false;
widget.editableNotifier?.isCellEditing.value = false;
cellBloc.add(const TextCellEvent.enableEdit(false));
}
Expand All @@ -100,52 +107,34 @@ class _TextCellState extends State<TextCardCell> {

@override
void didUpdateWidget(covariant oldWidget) {
_bindEditableNotifier();
if (oldWidget.editableNotifier != widget.editableNotifier) {
_bindEditableNotifier();
}
super.didUpdateWidget(oldWidget);
}

@override
Widget build(BuildContext context) {
final isTitle = cellBloc.cellController.fieldInfo.isPrimary;
return BlocProvider.value(
value: cellBloc,
child: BlocConsumer<TextCellBloc, TextCellState>(
child: BlocListener<TextCellBloc, TextCellState>(
listenWhen: (previous, current) => previous.content != current.content,
listener: (context, state) {
if (!state.enableEdit) {
_textEditingController.text = state.content;
}
},
builder: (context, state) {
final isTitle = cellBloc.cellController.fieldInfo.isPrimary;
if (state.content.isEmpty &&
state.enableEdit == false &&
focusWhenInit == false &&
!isTitle) {
return const SizedBox.shrink();
}

final icon = isTitle ? _buildIcon(state) : null;
final child = isTitle
? _buildTextField(state.enableEdit || focusWhenInit)
: _buildText(state.content);

return Row(
children: [
if (icon != null) ...[
icon,
const HSpace(4.0),
],
Expanded(child: child),
],
);
},
child: isTitle ? _buildTitle() : _buildText(),
),
);
}

@override
void dispose() {
_textEditingController.dispose();
widget.editableNotifier?.isCellEditing
.removeListener(_bindEditableNotifier);
focusNode.dispose();
cellBloc.close();
super.dispose();
Expand All @@ -170,61 +159,83 @@ class _TextCellState extends State<TextCardCell> {
return null;
}

Widget _buildText(String content) {
final text =
content.isEmpty ? LocaleKeys.grid_row_textPlaceholder.tr() : content;
final color = content.isEmpty ? Theme.of(context).hintColor : null;

return Padding(
padding: widget.style.padding,
child: Text(
text,
style: widget.style.textStyle.copyWith(color: color),
maxLines: widget.style.maxLines,
),
Widget _buildText() {
return BlocBuilder<TextCellBloc, TextCellState>(
builder: (context, state) {
final content = state.content;
final text = content.isEmpty
? LocaleKeys.grid_row_textPlaceholder.tr()
: content;
final color = content.isEmpty ? Theme.of(context).hintColor : null;

return Padding(
padding: widget.style.padding,
child: Text(
text,
style: widget.style.textStyle.copyWith(color: color),
maxLines: widget.style.maxLines,
),
);
},
);
}

Widget _buildTextField(bool isEditing) {
final padding =
widget.style.padding.add(const EdgeInsets.symmetric(vertical: 4.0));
return IgnorePointer(
ignoring: !isEditing,
child: CallbackShortcuts(
bindings: {
const SingleActivator(LogicalKeyboardKey.escape): () =>
focusNode.unfocus(),
},
child: TextField(
controller: _textEditingController,
focusNode: focusNode,
onChanged: (_) {
if (_textEditingController.value.composing.isCollapsed) {
cellBloc
.add(TextCellEvent.updateText(_textEditingController.text));
}
},
onEditingComplete: () => focusNode.unfocus(),
maxLines: isEditing ? null : 2,
minLines: 1,
textInputAction: TextInputAction.done,
readOnly: !isEditing,
enableInteractiveSelection: isEditing,
style: widget.style.titleTextStyle,
decoration: InputDecoration(
contentPadding: padding,
border: InputBorder.none,
enabledBorder: InputBorder.none,
isDense: true,
isCollapsed: true,
hintText: LocaleKeys.grid_row_titlePlaceholder.tr(),
hintStyle: widget.style.titleTextStyle.copyWith(
color: Theme.of(context).hintColor,
Widget _buildTitle() {
final textField = _buildTextField();
return BlocBuilder<TextCellBloc, TextCellState>(
builder: (context, state) {
final icon = _buildIcon(state);
return Row(
children: [
if (icon != null) ...[
icon,
const HSpace(4.0),
],
Expanded(child: textField),
],
);
},
);
}

Widget _buildTextField() {
return BlocSelector<TextCellBloc, TextCellState, bool>(
selector: (state) => state.enableEdit,
builder: (context, isEditing) {
return IgnorePointer(
ignoring: !isEditing,
child: CallbackShortcuts(
bindings: {
const SingleActivator(LogicalKeyboardKey.escape): () =>
focusNode.unfocus(),
},
child: TextField(
controller: _textEditingController,
focusNode: focusNode,
onEditingComplete: () => focusNode.unfocus(),
maxLines: isEditing ? null : 2,
minLines: 1,
textInputAction: TextInputAction.done,
readOnly: !isEditing,
enableInteractiveSelection: isEditing,
style: widget.style.titleTextStyle,
decoration: InputDecoration(
contentPadding: widget.style.padding
.add(const EdgeInsets.symmetric(vertical: 4.0)),
border: InputBorder.none,
enabledBorder: InputBorder.none,
isDense: true,
isCollapsed: true,
hintText: LocaleKeys.grid_row_titlePlaceholder.tr(),
hintStyle: widget.style.titleTextStyle.copyWith(
color: Theme.of(context).hintColor,
),
),
onTapOutside: (_) {},
),
),
onTapOutside: (_) {},
),
),
);
},
);
}
}
Loading