mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 22:57:12 -04:00
fix: kanban board card text input inconsistency (#5307)
This commit is contained in:
parent
bdc66103c8
commit
e28d463cb9
1 changed files with 99 additions and 86 deletions
|
@ -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: (_) {},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue