Skip to content

Commit

Permalink
fix: kanban board card text input inconsistency (#5307)
Browse files Browse the repository at this point in the history
  • Loading branch information
richardshiue committed May 10, 2024
1 parent bdc6610 commit e28d463
Showing 1 changed file with 100 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../editable_cell_builder.dart';
Expand Down Expand Up @@ -54,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 @@ -99,59 +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>(
listenWhen: (previous, current) =>
previous.content != current.content && !current.enableEdit,
child: BlocListener<TextCellBloc, TextCellState>(
listenWhen: (previous, current) => previous.content != current.content,
listener: (context, state) {
_textEditingController.text = state.content;
},
buildWhen: (previous, current) {
if (previous.content != current.content &&
_textEditingController.text == current.content) {
return false;
if (!state.enableEdit) {
_textEditingController.text = state.content;
}

return previous != current;
},
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 @@ -176,53 +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: 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: (_) {},
),
),
),
),
);
},
);
}
}

0 comments on commit e28d463

Please sign in to comment.