fix: kanban board card text input inconsistency (#5307)

This commit is contained in:
Richard Shiue 2024-05-10 11:25:22 +08:00
parent bdc66103c8
commit e28d463cb9

View file

@ -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/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../editable_cell_builder.dart'; import '../editable_cell_builder.dart';
@ -54,18 +55,26 @@ class _TextCellState extends State<TextCardCell> {
widget.cellContext, widget.cellContext,
).as(), ).as(),
); );
late final TextEditingController _textEditingController = late final TextEditingController _textEditingController;
TextEditingController(text: cellBloc.state.content);
final focusNode = SingleListenerFocusNode(); final focusNode = SingleListenerFocusNode();
bool focusWhenInit = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
focusWhenInit = widget.editableNotifier?.isCellEditing.value ?? false;
if (focusWhenInit) { _textEditingController = TextEditingController(text: cellBloc.state.content)
focusNode.requestFocus(); ..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 // If the focusNode lost its focus, the widget's editableNotifier will
@ -73,7 +82,6 @@ class _TextCellState extends State<TextCardCell> {
// end edit event. // end edit event.
focusNode.addListener(() { focusNode.addListener(() {
if (!focusNode.hasFocus) { if (!focusNode.hasFocus) {
focusWhenInit = false;
widget.editableNotifier?.isCellEditing.value = false; widget.editableNotifier?.isCellEditing.value = false;
cellBloc.add(const TextCellEvent.enableEdit(false)); cellBloc.add(const TextCellEvent.enableEdit(false));
} }
@ -99,52 +107,25 @@ class _TextCellState extends State<TextCardCell> {
@override @override
void didUpdateWidget(covariant oldWidget) { void didUpdateWidget(covariant oldWidget) {
_bindEditableNotifier(); if (oldWidget.editableNotifier != widget.editableNotifier) {
_bindEditableNotifier();
}
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isTitle = cellBloc.cellController.fieldInfo.isPrimary;
return BlocProvider.value( return BlocProvider.value(
value: cellBloc, value: cellBloc,
child: BlocConsumer<TextCellBloc, TextCellState>( child: BlocListener<TextCellBloc, TextCellState>(
listenWhen: (previous, current) => listenWhen: (previous, current) => previous.content != current.content,
previous.content != current.content && !current.enableEdit,
listener: (context, state) { listener: (context, state) {
_textEditingController.text = state.content; if (!state.enableEdit) {
}, _textEditingController.text = state.content;
buildWhen: (previous, current) {
if (previous.content != current.content &&
_textEditingController.text == current.content) {
return false;
} }
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(),
), ),
); );
} }
@ -152,6 +133,8 @@ class _TextCellState extends State<TextCardCell> {
@override @override
void dispose() { void dispose() {
_textEditingController.dispose(); _textEditingController.dispose();
widget.editableNotifier?.isCellEditing
.removeListener(_bindEditableNotifier);
focusNode.dispose(); focusNode.dispose();
cellBloc.close(); cellBloc.close();
super.dispose(); super.dispose();
@ -176,53 +159,83 @@ class _TextCellState extends State<TextCardCell> {
return null; return null;
} }
Widget _buildText(String content) { Widget _buildText() {
final text = return BlocBuilder<TextCellBloc, TextCellState>(
content.isEmpty ? LocaleKeys.grid_row_textPlaceholder.tr() : content; builder: (context, state) {
final color = content.isEmpty ? Theme.of(context).hintColor : null; 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( return Padding(
padding: widget.style.padding, padding: widget.style.padding,
child: Text( child: Text(
text, text,
style: widget.style.textStyle.copyWith(color: color), style: widget.style.textStyle.copyWith(color: color),
maxLines: widget.style.maxLines, maxLines: widget.style.maxLines,
), ),
);
},
); );
} }
Widget _buildTextField(bool isEditing) { Widget _buildTitle() {
final padding = final textField = _buildTextField();
widget.style.padding.add(const EdgeInsets.symmetric(vertical: 4.0)); return BlocBuilder<TextCellBloc, TextCellState>(
return IgnorePointer( builder: (context, state) {
ignoring: !isEditing, final icon = _buildIcon(state);
child: TextField( return Row(
controller: _textEditingController, children: [
focusNode: focusNode, if (icon != null) ...[
onChanged: (_) { icon,
if (_textEditingController.value.composing.isCollapsed) { const HSpace(4.0),
cellBloc.add(TextCellEvent.updateText(_textEditingController.text)); ],
} Expanded(child: textField),
}, ],
onEditingComplete: () => focusNode.unfocus(), );
maxLines: isEditing ? null : 2, },
minLines: 1, );
textInputAction: TextInputAction.done, }
readOnly: !isEditing,
enableInteractiveSelection: isEditing, Widget _buildTextField() {
style: widget.style.titleTextStyle, return BlocSelector<TextCellBloc, TextCellState, bool>(
decoration: InputDecoration( selector: (state) => state.enableEdit,
contentPadding: padding, builder: (context, isEditing) {
border: InputBorder.none, return IgnorePointer(
enabledBorder: InputBorder.none, ignoring: !isEditing,
isDense: true, child: CallbackShortcuts(
isCollapsed: true, bindings: {
hintText: LocaleKeys.grid_row_titlePlaceholder.tr(), const SingleActivator(LogicalKeyboardKey.escape): () =>
hintStyle: widget.style.titleTextStyle.copyWith( focusNode.unfocus(),
color: Theme.of(context).hintColor, },
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: (_) {},
),
), ),
), );
), },
); );
} }
} }