fix: kanban UX bugs (#5227)

* chore: improve title editing behavior

* chore: fix editable text field

* chore: fix autoscroll
This commit is contained in:
Richard Shiue 2024-04-30 17:35:03 +08:00 committed by GitHub
parent 33802fa62d
commit f3544375c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 180 additions and 150 deletions

View File

@ -11,6 +11,8 @@ const uint8_t *sync_event(const uint8_t *input, uintptr_t len);
int32_t set_stream_port(int64_t port); int32_t set_stream_port(int64_t port);
int32_t set_log_stream_port(int64_t port);
void link_me_please(void); void link_me_please(void);
void rust_log(int64_t level, const char *data); void rust_log(int64_t level, const char *data);

View File

@ -1,6 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/database/board/presentation/widgets/board_column_header.dart'; import 'package:appflowy/plugins/database/board/presentation/widgets/board_column_header.dart';
import 'package:appflowy/plugins/database/widgets/card/container/card_container.dart'; import 'package:appflowy/plugins/database/widgets/card/card.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_board/appflowy_board.dart'; import 'package:appflowy_board/appflowy_board.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -22,13 +22,15 @@ void main() {
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board); await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
final findFirstCard = find.descendant( final firstCard = find.byType(RowCard).first;
of: find.byType(AppFlowyGroupCard),
matching: find.byType(Text),
);
Text firstCardText = tester.firstWidget(findFirstCard); expect(
expect(firstCardText.data, defaultFirstCardName); find.descendant(
of: firstCard,
matching: find.text(defaultFirstCardName),
),
findsOneWidget,
);
await tester.tap( await tester.tap(
find find
@ -45,7 +47,7 @@ void main() {
const newCardName = 'Card 4'; const newCardName = 'Card 4';
await tester.enterText( await tester.enterText(
find.descendant( find.descendant(
of: find.byType(RowCardContainer), of: firstCard,
matching: find.byType(TextField), matching: find.byType(TextField),
), ),
newCardName, newCardName,
@ -55,8 +57,13 @@ void main() {
await tester.tap(find.byType(AppFlowyBoard)); await tester.tap(find.byType(AppFlowyBoard));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
firstCardText = tester.firstWidget(findFirstCard); expect(
expect(firstCardText.data, newCardName); find.descendant(
of: find.byType(RowCard).first,
matching: find.text(newCardName),
),
findsOneWidget,
);
}); });
testWidgets('from footer', (tester) async { testWidgets('from footer', (tester) async {
@ -65,13 +72,15 @@ void main() {
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board); await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
final findLastCard = find.descendant( final lastCard = find.byType(RowCard).last;
of: find.byType(AppFlowyGroupCard),
matching: find.byType(Text),
);
Text? lastCardText = tester.widgetList(findLastCard).last as Text; expect(
expect(lastCardText.data, defaultLastCardName); find.descendant(
of: lastCard,
matching: find.text(defaultLastCardName),
),
findsOneWidget,
);
await tester.tap( await tester.tap(
find find
@ -81,12 +90,11 @@ void main() {
) )
.at(1), .at(1),
); );
await tester.pumpAndSettle();
const newCardName = 'Card 4'; const newCardName = 'Card 4';
await tester.enterText( await tester.enterText(
find.descendant( find.descendant(
of: find.byType(RowCardContainer), of: lastCard,
matching: find.byType(TextField), matching: find.byType(TextField),
), ),
newCardName, newCardName,
@ -96,8 +104,13 @@ void main() {
await tester.tap(find.byType(AppFlowyBoard)); await tester.tap(find.byType(AppFlowyBoard));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
lastCardText = tester.widgetList(findLastCard).last as Text; expect(
expect(lastCardText.data, newCardName); find.descendant(
of: find.byType(RowCard).last,
matching: find.text(newCardName),
),
findsOneWidget,
);
}); });
}); });
} }

View File

@ -1,10 +1,10 @@
import 'package:appflowy/plugins/database/widgets/card/card.dart';
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
import 'package:appflowy/plugins/database/widgets/row/row_property.dart'; import 'package:appflowy/plugins/database/widgets/row/row_property.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:appflowy_board/appflowy_board.dart';
import '../../shared/util.dart'; import '../../shared/util.dart';
@ -20,7 +20,7 @@ void main() {
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board); await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
final card1 = find.ancestor( final card1 = find.ancestor(
of: find.text(card1Name), of: find.text(card1Name),
matching: find.byType(AppFlowyGroupCard), matching: find.byType(RowCard),
); );
final doingGroup = find.text('Doing'); final doingGroup = find.text('Doing');
final doingGroupCenter = tester.getCenter(doingGroup); final doingGroupCenter = tester.getCenter(doingGroup);

View File

@ -68,6 +68,8 @@ class CellController<T, D> {
Timer? _loadDataOperation; Timer? _loadDataOperation;
Timer? _saveDataOperation; Timer? _saveDataOperation;
Completer? _completer;
RowId get rowId => _cellContext.rowId; RowId get rowId => _cellContext.rowId;
String get fieldId => _cellContext.fieldId; String get fieldId => _cellContext.fieldId;
FieldInfo get fieldInfo => _fieldController.getField(_cellContext.fieldId)!; FieldInfo get fieldInfo => _fieldController.getField(_cellContext.fieldId)!;
@ -192,6 +194,7 @@ class CellController<T, D> {
_loadDataOperation?.cancel(); _loadDataOperation?.cancel();
if (debounce) { if (debounce) {
_saveDataOperation?.cancel(); _saveDataOperation?.cancel();
_completer = Completer();
_saveDataOperation = Timer(const Duration(milliseconds: 300), () async { _saveDataOperation = Timer(const Duration(milliseconds: 300), () async {
final result = await _cellDataPersistence.save( final result = await _cellDataPersistence.save(
viewId: viewId, viewId: viewId,
@ -199,6 +202,7 @@ class CellController<T, D> {
data: data, data: data,
); );
onFinish?.call(result); onFinish?.call(result);
_completer?.complete();
}); });
} else { } else {
final result = await _cellDataPersistence.save( final result = await _cellDataPersistence.save(
@ -241,6 +245,7 @@ class CellController<T, D> {
); );
_loadDataOperation?.cancel(); _loadDataOperation?.cancel();
await _completer?.future;
_saveDataOperation?.cancel(); _saveDataOperation?.cancel();
_cellDataNotifier?.dispose(); _cellDataNotifier?.dispose();
_cellDataNotifier = null; _cellDataNotifier = null;

View File

@ -59,7 +59,7 @@ class FieldCellState with _$FieldCellState {
factory FieldCellState.initial(FieldInfo fieldInfo) => FieldCellState( factory FieldCellState.initial(FieldInfo fieldInfo) => FieldCellState(
fieldInfo: fieldInfo, fieldInfo: fieldInfo,
isResizing: false, isResizing: false,
width: fieldInfo.fieldSettings!.width.toDouble(), width: fieldInfo.width!.toDouble(),
resizeStart: 0, resizeStart: 0,
); );

View File

@ -102,11 +102,12 @@ class BoardPage extends StatelessWidget {
)..add(const BoardEvent.initial()), )..add(const BoardEvent.initial()),
child: BlocBuilder<BoardBloc, BoardState>( child: BlocBuilder<BoardBloc, BoardState>(
buildWhen: (p, c) => p.loadingState != c.loadingState, buildWhen: (p, c) => p.loadingState != c.loadingState,
builder: (context, state) => state.loadingState.map( builder: (context, state) => state.loadingState.when(
loading: (_) => const Center( loading: () => const Center(
child: CircularProgressIndicator.adaptive(), child: CircularProgressIndicator.adaptive(),
), ),
finish: (result) => result.successOrFail.fold( idle: () => const SizedBox.shrink(),
finish: (result) => result.fold(
(_) => PlatformExtension.isMobile (_) => PlatformExtension.isMobile
? const MobileBoardContent() ? const MobileBoardContent()
: DesktopBoardContent(onEditStateChanged: onEditStateChanged), : DesktopBoardContent(onEditStateChanged: onEditStateChanged),
@ -121,7 +122,6 @@ class BoardPage extends StatelessWidget {
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(), howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
), ),
), ),
idle: (_) => const SizedBox.shrink(),
), ),
), ),
); );
@ -154,6 +154,10 @@ class _DesktopBoardContentState extends State<DesktopBoardContent> {
stretchGroupHeight: false, stretchGroupHeight: false,
); );
late final cellBuilder = CardCellBuilder(
databaseController: context.read<BoardBloc>().databaseController,
);
@override @override
void dispose() { void dispose() {
scrollController.dispose(); scrollController.dispose();
@ -164,7 +168,6 @@ class _DesktopBoardContentState extends State<DesktopBoardContent> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocListener<BoardBloc, BoardState>( return BlocListener<BoardBloc, BoardState>(
listener: (context, state) { listener: (context, state) {
_handleEditStateChanged(state, context);
widget.onEditStateChanged?.call(); widget.onEditStateChanged?.call();
}, },
child: BlocBuilder<BoardBloc, BoardState>( child: BlocBuilder<BoardBloc, BoardState>(
@ -182,7 +185,7 @@ class _DesktopBoardContentState extends State<DesktopBoardContent> {
leading: HiddenGroupsColumn(margin: config.groupHeaderPadding), leading: HiddenGroupsColumn(margin: config.groupHeaderPadding),
trailing: showCreateGroupButton trailing: showCreateGroupButton
? BoardTrailing(scrollController: scrollController) ? BoardTrailing(scrollController: scrollController)
: null, : const HSpace(40),
headerBuilder: (_, groupData) => BlocProvider<BoardBloc>.value( headerBuilder: (_, groupData) => BlocProvider<BoardBloc>.value(
value: context.read<BoardBloc>(), value: context.read<BoardBloc>(),
child: BoardColumnHeader( child: BoardColumnHeader(
@ -203,16 +206,6 @@ class _DesktopBoardContentState extends State<DesktopBoardContent> {
); );
} }
void _handleEditStateChanged(BoardState state, BuildContext context) {
if (state.isEditingRow && state.editingRow != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (state.editingRow!.index == null) {
scrollManager.scrollToBottom(state.editingRow!.group.groupId);
}
});
}
}
Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) { Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) {
return Padding( return Padding(
padding: config.groupFooterPadding, padding: config.groupFooterPadding,
@ -257,14 +250,13 @@ class _DesktopBoardContentState extends State<DesktopBoardContent> {
final databaseController = boardBloc.databaseController; final databaseController = boardBloc.databaseController;
final viewId = boardBloc.viewId; final viewId = boardBloc.viewId;
final cellBuilder = CardCellBuilder(databaseController: databaseController);
final isEditing = boardBloc.state.isEditingRow && final isEditing = boardBloc.state.isEditingRow &&
boardBloc.state.editingRow?.row.id == groupItem.row.id; boardBloc.state.editingRow?.row.id == groupItem.row.id;
final groupItemId = "${groupData.group.groupId}${groupItem.row.id}"; final groupItemId = "${groupData.group.groupId}${groupItem.row.id}";
final rowMeta = rowInfo?.rowMeta ?? groupItem.row; final rowMeta = rowInfo?.rowMeta ?? groupItem.row;
return AppFlowyGroupCard( return Container(
key: ValueKey(groupItemId), key: ValueKey(groupItemId),
margin: config.cardMargin, margin: config.cardMargin,
decoration: _makeBoxDecoration(context), decoration: _makeBoxDecoration(context),
@ -412,52 +404,50 @@ class _BoardTrailingState extends State<BoardTrailing> {
} }
}); });
return Padding( return Container(
padding: const EdgeInsets.only(left: 8.0, top: 12), padding: const EdgeInsets.only(left: 8.0, top: 12, right: 40),
child: Align( alignment: AlignmentDirectional.topStart,
alignment: AlignmentDirectional.topStart, child: AnimatedSwitcher(
child: AnimatedSwitcher( duration: const Duration(milliseconds: 300),
duration: const Duration(milliseconds: 300), child: isEditing
child: isEditing ? SizedBox(
? SizedBox( width: 256,
width: 256, child: Padding(
child: Padding( padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(8.0), child: TextField(
child: TextField( controller: _textController,
controller: _textController, focusNode: _focusNode,
focusNode: _focusNode, decoration: InputDecoration(
decoration: InputDecoration( suffixIcon: Padding(
suffixIcon: Padding( padding: const EdgeInsets.only(left: 4, bottom: 8.0),
padding: const EdgeInsets.only(left: 4, bottom: 8.0), child: FlowyIconButton(
child: FlowyIconButton( icon: const FlowySvg(FlowySvgs.close_filled_m),
icon: const FlowySvg(FlowySvgs.close_filled_m), hoverColor: Colors.transparent,
hoverColor: Colors.transparent, onPressed: () => _textController.clear(),
onPressed: () => _textController.clear(),
),
), ),
suffixIconConstraints:
BoxConstraints.loose(const Size(20, 24)),
border: const UnderlineInputBorder(),
contentPadding: const EdgeInsets.fromLTRB(8, 4, 8, 8),
isDense: true,
), ),
style: Theme.of(context).textTheme.bodySmall, suffixIconConstraints:
onSubmitted: (groupName) => context BoxConstraints.loose(const Size(20, 24)),
.read<BoardBloc>() border: const UnderlineInputBorder(),
.add(BoardEvent.createGroup(groupName)), contentPadding: const EdgeInsets.fromLTRB(8, 4, 8, 8),
isDense: true,
), ),
), style: Theme.of(context).textTheme.bodySmall,
) onSubmitted: (groupName) => context
: FlowyTooltip( .read<BoardBloc>()
message: LocaleKeys.board_column_createNewColumn.tr(), .add(BoardEvent.createGroup(groupName)),
child: FlowyIconButton(
width: 26,
icon: const FlowySvg(FlowySvgs.add_s),
iconColorOnHover: Theme.of(context).colorScheme.onSurface,
onPressed: () => setState(() => isEditing = true),
), ),
), ),
), )
: FlowyTooltip(
message: LocaleKeys.board_column_createNewColumn.tr(),
child: FlowyIconButton(
width: 26,
icon: const FlowySvg(FlowySvgs.add_s),
iconColorOnHover: Theme.of(context).colorScheme.onSurface,
onPressed: () => setState(() => isEditing = true),
),
),
), ),
); );
} }

View File

@ -45,14 +45,14 @@ class HiddenGroupsColumn extends StatelessWidget {
? SizedBox( ? SizedBox(
height: 50, height: 50,
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 40, right: 8), padding: const EdgeInsets.only(left: 80, right: 8),
child: Center( child: Center(
child: _collapseExpandIcon(context, isCollapsed), child: _collapseExpandIcon(context, isCollapsed),
), ),
), ),
) )
: SizedBox( : SizedBox(
width: 234, width: 274,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -60,7 +60,7 @@ class HiddenGroupsColumn extends StatelessWidget {
height: 50, height: 50,
child: Padding( child: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: 40 + margin.left, left: 80 + margin.left,
right: margin.right + 4, right: margin.right + 4,
), ),
child: Row( child: Row(

View File

@ -167,10 +167,13 @@ class _EventCardState extends State<EventCard> {
), ),
); );
}, },
child: Container( child: Material(
padding: widget.padding, color: Colors.transparent,
decoration: decoration, child: Container(
child: card, padding: widget.padding,
decoration: decoration,
child: card,
),
), ),
); );

View File

@ -188,7 +188,8 @@ class _CalendarPageState extends State<CalendarPage> {
return Padding( return Padding(
padding: PlatformExtension.isMobile padding: PlatformExtension.isMobile
? CalendarSize.contentInsetsMobile ? CalendarSize.contentInsetsMobile
: CalendarSize.contentInsets, : CalendarSize.contentInsets +
const EdgeInsets.symmetric(horizontal: 40),
child: ScrollConfiguration( child: ScrollConfiguration(
behavior: behavior:
ScrollConfiguration.of(context).copyWith(scrollbars: false), ScrollConfiguration.of(context).copyWith(scrollbars: false),

View File

@ -154,7 +154,11 @@ class _GridPageState extends State<GridPage> {
loading: (_) => loading: (_) =>
const Center(child: CircularProgressIndicator.adaptive()), const Center(child: CircularProgressIndicator.adaptive()),
finish: (result) => result.successOrFail.fold( finish: (result) => result.successOrFail.fold(
(_) => GridShortcuts(child: GridPageContent(view: widget.view)), (_) => GridShortcuts(
child: GridPageContent(
view: widget.view,
),
),
(err) => FlowyErrorPage.message( (err) => FlowyErrorPage.message(
err.toString(), err.toString(),
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(), howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
@ -234,7 +238,9 @@ class _GridPageContentState extends State<GridPageContent> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_GridHeader(headerScrollController: headerScrollController), _GridHeader(
headerScrollController: headerScrollController,
),
_GridRows( _GridRows(
viewId: widget.view.id, viewId: widget.view.id,
scrollController: _scrollController, scrollController: _scrollController,
@ -498,7 +504,7 @@ class _PositionedCalculationsRowState
left: 0, left: 0,
right: 0, right: 0,
child: Container( child: Container(
margin: EdgeInsets.only(left: GridSize.horizontalHeaderPadding), margin: EdgeInsets.only(left: GridSize.horizontalHeaderPadding + 40),
padding: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.only(bottom: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).canvasColor, color: Theme.of(context).canvasColor,

View File

@ -12,11 +12,12 @@ class GridLayout {
element.visibility != null && element.visibility != null &&
element.visibility != FieldVisibility.AlwaysHidden, element.visibility != FieldVisibility.AlwaysHidden,
) )
.map((fieldInfo) => fieldInfo.fieldSettings!.width.toDouble()) .map((fieldInfo) => fieldInfo.width!.toDouble())
.reduce((value, element) => value + element); .reduce((value, element) => value + element);
return fieldsWidth + return fieldsWidth +
GridSize.horizontalHeaderPadding + GridSize.horizontalHeaderPadding +
40 +
GridSize.trailHeaderPadding; GridSize.trailHeaderPadding;
} }
} }

View File

@ -37,7 +37,7 @@ class GridCalculationsRow extends StatelessWidget {
key: Key( key: Key(
'${field.id}-${state.calculationsByFieldId[field.id]?.id}', '${field.id}-${state.calculationsByFieldId[field.id]?.id}',
), ),
width: field.fieldSettings!.width.toDouble(), width: field.width!.toDouble(),
fieldInfo: field, fieldInfo: field,
calculation: state.calculationsByFieldId[field.id], calculation: state.calculationsByFieldId[field.id],
), ),

View File

@ -42,9 +42,8 @@ class GridRowBottomBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: GridSize.footerContentInsets, padding: GridSize.footerContentInsets + const EdgeInsets.only(left: 40),
height: GridSize.footerHeight, height: GridSize.footerHeight,
// margin: const EdgeInsets.only(bottom: 8, top: 8),
child: const GridAddRowButton(), child: const GridAddRowButton(),
); );
} }

View File

@ -139,7 +139,7 @@ class _GridHeaderState extends State<_GridHeader> {
} }
Widget _cellLeading() { Widget _cellLeading() {
return SizedBox(width: GridSize.horizontalHeaderPadding); return SizedBox(width: GridSize.horizontalHeaderPadding + 40);
} }
} }

View File

@ -112,7 +112,7 @@ class _RowLeadingState extends State<_RowLeading> {
child: Consumer<RegionStateNotifier>( child: Consumer<RegionStateNotifier>(
builder: (context, state, _) { builder: (context, state, _) {
return SizedBox( return SizedBox(
width: GridSize.horizontalHeaderPadding, width: GridSize.horizontalHeaderPadding + 40,
child: state.onEnter ? _activeWidget() : null, child: state.onEnter ? _activeWidget() : null,
); );
}, },
@ -122,7 +122,7 @@ class _RowLeadingState extends State<_RowLeading> {
Widget _activeWidget() { Widget _activeWidget() {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
const InsertRowButton(), const InsertRowButton(),
if (isDraggable) if (isDraggable)
@ -246,7 +246,7 @@ class RowContent extends StatelessWidget {
EditableCellStyle.desktopGrid, EditableCellStyle.desktopGrid,
); );
return CellContainer( return CellContainer(
width: fieldInfo.fieldSettings!.width.toDouble(), width: fieldInfo.width!.toDouble(),
isPrimary: fieldInfo.field.isPrimary, isPrimary: fieldInfo.field.isPrimary,
accessoryBuilder: (buildContext) { accessoryBuilder: (buildContext) {
final builder = child.accessoryBuilder; final builder = child.accessoryBuilder;

View File

@ -24,7 +24,7 @@ class TabBarHeader extends StatelessWidget {
return Container( return Container(
height: 30, height: 30,
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: GridSize.horizontalHeaderPadding, horizontal: GridSize.horizontalHeaderPadding + 40,
), ),
child: Stack( child: Stack(
children: [ children: [

View File

@ -286,4 +286,7 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
), ),
); );
} }
@override
EdgeInsets get contentPadding => const EdgeInsets.only(top: 28);
} }

View File

@ -83,7 +83,9 @@ class _TextCellState extends State<TextCardCell> {
void _bindEditableNotifier() { void _bindEditableNotifier() {
widget.editableNotifier?.isCellEditing.addListener(() { widget.editableNotifier?.isCellEditing.addListener(() {
if (!mounted) return; if (!mounted) {
return;
}
final isEditing = widget.editableNotifier?.isCellEditing.value ?? false; final isEditing = widget.editableNotifier?.isCellEditing.value ?? false;
if (isEditing) { if (isEditing) {
@ -106,15 +108,14 @@ class _TextCellState extends State<TextCardCell> {
return BlocProvider.value( return BlocProvider.value(
value: cellBloc, value: cellBloc,
child: BlocConsumer<TextCellBloc, TextCellState>( child: BlocConsumer<TextCellBloc, TextCellState>(
listenWhen: (previous, current) =>
previous.content != current.content && !current.enableEdit,
listener: (context, state) { listener: (context, state) {
if (_textEditingController.text != state.content) { _textEditingController.text = state.content;
_textEditingController.text = state.content;
}
}, },
buildWhen: (previous, current) { buildWhen: (previous, current) {
if (previous.content != current.content && if (previous.content != current.content &&
_textEditingController.text == current.content && _textEditingController.text == current.content) {
current.enableEdit) {
return false; return false;
} }
@ -129,10 +130,10 @@ class _TextCellState extends State<TextCardCell> {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
final icon = _buildIcon(state, isTitle); final icon = isTitle ? _buildIcon(state) : null;
final child = state.enableEdit || focusWhenInit final child = isTitle
? _buildTextField() ? _buildTextField(state.enableEdit || focusWhenInit)
: _buildText(state, isTitle); : _buildText(state.content);
return Row( return Row(
children: [ children: [
@ -156,10 +157,7 @@ class _TextCellState extends State<TextCardCell> {
super.dispose(); super.dispose();
} }
Widget? _buildIcon(TextCellState state, bool isTitle) { Widget? _buildIcon(TextCellState state) {
if (!isTitle) {
return null;
}
if (state.emoji.isNotEmpty) { if (state.emoji.isNotEmpty) {
return Text( return Text(
state.emoji, state.emoji,
@ -178,43 +176,52 @@ class _TextCellState extends State<TextCardCell> {
return null; return null;
} }
Widget _buildText(TextCellState state, bool isTitle) { Widget _buildText(String content) {
final text = state.content.isEmpty final text =
? isTitle content.isEmpty ? LocaleKeys.grid_row_textPlaceholder.tr() : content;
? LocaleKeys.grid_row_titlePlaceholder.tr() final color = content.isEmpty ? Theme.of(context).hintColor : null;
: LocaleKeys.grid_row_textPlaceholder.tr()
: state.content;
final color = state.content.isEmpty ? Theme.of(context).hintColor : null;
final textStyle =
isTitle ? widget.style.titleTextStyle : widget.style.textStyle;
return Padding( return Padding(
padding: widget.style.padding, padding: widget.style.padding,
child: Text( child: Text(
text, text,
style: textStyle.copyWith(color: color), style: widget.style.textStyle.copyWith(color: color),
maxLines: widget.style.maxLines, maxLines: widget.style.maxLines,
), ),
); );
} }
Widget _buildTextField() { Widget _buildTextField(bool isEditing) {
final padding = final padding =
widget.style.padding.add(const EdgeInsets.symmetric(vertical: 4.0)); widget.style.padding.add(const EdgeInsets.symmetric(vertical: 4.0));
return TextField( return IgnorePointer(
controller: _textEditingController, ignoring: !isEditing,
focusNode: focusNode, child: TextField(
onChanged: (_) => controller: _textEditingController,
cellBloc.add(TextCellEvent.updateText(_textEditingController.text)), focusNode: focusNode,
onEditingComplete: () => focusNode.unfocus(), onChanged: (_) {
maxLines: null, if (_textEditingController.value.composing.isCollapsed) {
style: widget.style.titleTextStyle, cellBloc.add(TextCellEvent.updateText(_textEditingController.text));
decoration: InputDecoration( }
contentPadding: padding, },
border: InputBorder.none, onEditingComplete: () => focusNode.unfocus(),
isDense: true, maxLines: isEditing ? null : 2,
isCollapsed: true, minLines: 1,
hintText: LocaleKeys.grid_row_titlePlaceholder.tr(), 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,
),
),
), ),
); );
} }

View File

@ -56,7 +56,7 @@ CardCellStyleMap desktopBoardCardCellStyleMap(BuildContext context) {
FieldType.RichText: TextCardCellStyle( FieldType.RichText: TextCardCellStyle(
padding: padding, padding: padding,
textStyle: textStyle, textStyle: textStyle,
maxLines: null, maxLines: 2,
titleTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( titleTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),

View File

@ -180,8 +180,7 @@ class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
return; return;
} }
final newVisiblity = final newVisiblity = widget.fieldInfo.visibility!.toggle();
widget.fieldInfo.fieldSettings!.visibility.toggle();
context.read<DatabasePropertyBloc>().add( context.read<DatabasePropertyBloc>().add(
DatabasePropertyEvent.setFieldVisibility( DatabasePropertyEvent.setFieldVisibility(
widget.fieldInfo.id, widget.fieldInfo.id,

View File

@ -44,11 +44,11 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "15a3a50" ref: f88b4ce01d2728c05125cbe9170013f4a7c85a31
resolved-ref: "15a3a5071ffdb002ffaefda9df343b6800844d8d" resolved-ref: f88b4ce01d2728c05125cbe9170013f4a7c85a31
url: "https://github.com/AppFlowy-IO/appflowy-board.git" url: "https://github.com/AppFlowy-IO/appflowy-board.git"
source: git source: git
version: "0.1.1" version: "0.1.2"
appflowy_editor: appflowy_editor:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1405,10 +1405,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: provider name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.1" version: "6.1.2"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:

View File

@ -41,9 +41,10 @@ dependencies:
flowy_svg: flowy_svg:
path: packages/flowy_svg path: packages/flowy_svg
appflowy_board: appflowy_board:
# path: ../../../appflowy-board
git: git:
url: https://github.com/AppFlowy-IO/appflowy-board.git url: https://github.com/AppFlowy-IO/appflowy-board.git
ref: 15a3a50 ref: f88b4ce01d2728c05125cbe9170013f4a7c85a31
appflowy_result: appflowy_result:
path: packages/appflowy_result path: packages/appflowy_result
appflowy_editor_plugins: ^0.0.2 appflowy_editor_plugins: ^0.0.2