From aa25aa44740e4d791d5511fc4c5f07bba40c7a18 Mon Sep 17 00:00:00 2001 From: Vincenzo De Petris <37916223+vincendep@users.noreply.github.com> Date: Wed, 13 Sep 2023 05:18:07 +0200 Subject: [PATCH 01/14] fix: DateCardCellState date formatting (#3387) * fix: DateCardCellState date formatting * refactor: use string interpolation --- .../widgets/card/bloc/date_card_cell_bloc.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/date_card_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/date_card_cell_bloc.dart index 40ddc24438dd..226710be06c9 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/date_card_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/bloc/date_card_cell_bloc.dart @@ -80,7 +80,11 @@ class DateCardCellState with _$DateCardCellState { String _dateStrFromCellData(DateCellDataPB? cellData) { String dateStr = ""; if (cellData != null) { - dateStr = "${cellData.date} ${cellData.time}"; + if (cellData.includeTime) { + dateStr = '${cellData.date} ${cellData.time}'; + } else { + dateStr = cellData.date; + } } return dateStr; } From ef6f9a3175bf582b0515a0be5c44e61cdd6b1fd4 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen <42929161+Xazin@users.noreply.github.com> Date: Wed, 13 Sep 2023 05:36:22 +0200 Subject: [PATCH 02/14] fix: reorder rows on windows (#3279) * fix: reorder rows on windows Closes: #3208 * fix: unused import --- .../grid/presentation/grid_page.dart | 87 +++++++------------ .../grid/presentation/widgets/row/row.dart | 8 +- 2 files changed, 34 insertions(+), 61 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart index 0eefacda478d..8546ca3d9602 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/application/row/row_service.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_setting_bar.dart'; @@ -257,59 +255,38 @@ class _GridRows extends StatelessWidget { GridState state, List rowInfos, ) { - if (Platform.isWindows) { - // Workaround: On Windows, the focusing of the text cell is not working - // properly when the list is reorderable. So using the ListView instead. - return ListView.builder( - controller: scrollController.verticalController, - itemCount: rowInfos.length + 1, // the extra item is the footer - itemBuilder: (context, index) { - if (index < rowInfos.length) { - final rowInfo = rowInfos[index]; - return _renderRow( - context, - rowInfo.rowId, - isDraggable: false, - index: index, - ); - } - return const GridRowBottomBar(key: Key('gridFooter')); - }, - ); - } else { - return ReorderableListView.builder( - /// TODO(Xazin): Resolve inconsistent scrollbar behavior - /// This is a workaround related to - /// https://github.com/flutter/flutter/issues/25652 - cacheExtent: 5000, - scrollController: scrollController.verticalController, - buildDefaultDragHandles: false, - proxyDecorator: (child, index, animation) => Material( - color: Colors.white.withOpacity(.1), - child: Opacity(opacity: .5, child: child), - ), - onReorder: (fromIndex, newIndex) { - final toIndex = newIndex > fromIndex ? newIndex - 1 : newIndex; - if (fromIndex == toIndex) { - return; - } - context.read().add(GridEvent.moveRow(fromIndex, toIndex)); - }, - itemCount: rowInfos.length + 1, // the extra item is the footer - itemBuilder: (context, index) { - if (index < rowInfos.length) { - final rowInfo = rowInfos[index]; - return _renderRow( - context, - rowInfo.rowId, - isDraggable: state.reorderable, - index: index, - ); - } - return const GridRowBottomBar(key: Key('gridFooter')); - }, - ); - } + return ReorderableListView.builder( + /// TODO(Xazin): Resolve inconsistent scrollbar behavior + /// This is a workaround related to + /// https://github.com/flutter/flutter/issues/25652 + cacheExtent: 5000, + scrollController: scrollController.verticalController, + buildDefaultDragHandles: false, + proxyDecorator: (child, index, animation) => Material( + color: Colors.white.withOpacity(.1), + child: Opacity(opacity: .5, child: child), + ), + onReorder: (fromIndex, newIndex) { + final toIndex = newIndex > fromIndex ? newIndex - 1 : newIndex; + if (fromIndex == toIndex) { + return; + } + context.read().add(GridEvent.moveRow(fromIndex, toIndex)); + }, + itemCount: rowInfos.length + 1, // the extra item is the footer + itemBuilder: (context, index) { + if (index < rowInfos.length) { + final rowInfo = rowInfos[index]; + return _renderRow( + context, + rowInfo.rowId, + isDraggable: state.reorderable, + index: index, + ); + } + return const GridRowBottomBar(key: Key('gridFooter')); + }, + ); } Widget _renderRow( diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart index 02f7f5e70c8d..8ce1174830fb 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart @@ -161,16 +161,12 @@ class _RowLeadingState extends State<_RowLeading> { index: widget.index!, child: RowMenuButton( isDragEnabled: isDraggable, - openMenu: () { - popoverController.show(); - }, + openMenu: popoverController.show, ), ), ] else ...[ RowMenuButton( - openMenu: () { - popoverController.show(); - }, + openMenu: popoverController.show, ), ], ], From 1ca130d7de87ec39b93696fb07f711ddd9900489 Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Wed, 13 Sep 2023 19:10:08 +0800 Subject: [PATCH 03/14] feat: revamp row detail page UI (#3328) * feat: revamp row detail page UI * chore: some minor details and fix tests * fix: fix tests * chore: remove unused field * chore: code cleanup * test: add reordering fields in row page tests * chore: remove duplicate and delete row events * chore: timestamp cell ui adjustment * chore: remove unused code * test: fix new integration tests --- .../database_calendar_test.dart | 5 +- .../database_row_page_test.dart | 39 ++- .../util/database_test_op.dart | 44 +++- .../application/database_controller.dart | 8 +- .../application/row/row_banner_bloc.dart | 6 +- .../grid/application/row/row_detail_bloc.dart | 50 ++-- .../widgets/header/field_cell.dart | 8 +- .../grid/presentation/widgets/row/action.dart | 5 +- .../widgets/row/accessory/cell_accessory.dart | 79 ++---- .../widgets/row/cell_builder.dart | 2 + .../cells/checkbox_cell/checkbox_cell.dart | 22 +- .../row/cells/date_cell/date_cell.dart | 39 ++- .../row/cells/number_cell/number_cell.dart | 27 +- .../cells/select_option_cell/extension.dart | 3 +- .../select_option_cell.dart | 18 +- .../row/cells/text_cell/text_cell.dart | 11 +- .../cells/timestamp_cell/timestamp_cell.dart | 59 ++--- .../database_view/widgets/row/row_action.dart | 143 +++-------- .../database_view/widgets/row/row_banner.dart | 175 ++++++++----- .../database_view/widgets/row/row_detail.dart | 134 ++-------- .../widgets/row/row_property.dart | 240 +++++++++++++++--- .../flowy_icons/16x/details_horizontal.svg | 5 + frontend/resources/flowy_icons/16x/emoji.svg | 13 + 23 files changed, 648 insertions(+), 487 deletions(-) create mode 100644 frontend/resources/flowy_icons/16x/details_horizontal.svg create mode 100644 frontend/resources/flowy_icons/16x/emoji.svg diff --git a/frontend/appflowy_flutter/integration_test/database_calendar_test.dart b/frontend/appflowy_flutter/integration_test/database_calendar_test.dart index dfd42fed1946..ca16f801d58b 100644 --- a/frontend/appflowy_flutter/integration_test/database_calendar_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_calendar_test.dart @@ -9,7 +9,7 @@ import 'util/util.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - group('calendar database view', () { + group('calendar', () { testWidgets('update calendar layout', (tester) async { await tester.initializeAppFlowy(); await tester.tapGoButton(); @@ -116,6 +116,7 @@ void main() { tester.assertRowDetailPageOpened(); // Duplicate the event + await tester.tapRowDetailPageRowActionButton(); await tester.tapRowDetailPageDuplicateRowButton(); await tester.dismissRowDetailPage(); @@ -125,6 +126,7 @@ void main() { // Delete an event await tester.openCalendarEvent(index: 1); + await tester.tapRowDetailPageRowActionButton(); await tester.tapRowDetailPageDeleteRowButton(); // Check that there is 1 event @@ -155,6 +157,7 @@ void main() { // Delete the event await tester.openCalendarEvent(index: 0, date: sameDayNextWeek); + await tester.tapRowDetailPageRowActionButton(); await tester.tapRowDetailPageDeleteRowButton(); // Create a new event in today's calendar cell diff --git a/frontend/appflowy_flutter/integration_test/database_row_page_test.dart b/frontend/appflowy_flutter/integration_test/database_row_page_test.dart index 373ef7b0de21..6b88fafab824 100644 --- a/frontend/appflowy_flutter/integration_test/database_row_page_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_row_page_test.dart @@ -135,7 +135,40 @@ void main() { } }); - testWidgets('check document is exist in row detail page', (tester) async { + testWidgets('change order of fields and cells', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapGoButton(); + + // Create a new grid + await tester.createNewPageWithName(layout: ViewLayoutPB.Grid); + + // Hover first row and then open the row page + await tester.openFirstRowDetailPage(); + + // Assert that the first field in the row details page is the select + // option tyoe + tester.assertFirstFieldInRowDetailByType(FieldType.SingleSelect); + + // Reorder first field in list + final gesture = await tester.hoverOnFieldInRowDetail(index: 0); + await tester.pumpAndSettle(); + await tester.reorderFieldInRowDetail(offset: 30); + + // Orders changed, now the checkbox is first + tester.assertFirstFieldInRowDetailByType(FieldType.Checkbox); + await gesture.removePointer(); + await tester.pumpAndSettle(); + + // Reorder second field in list + await tester.hoverOnFieldInRowDetail(index: 1); + await tester.pumpAndSettle(); + await tester.reorderFieldInRowDetail(offset: -30); + + // First field is now back to select option + tester.assertFirstFieldInRowDetailByType(FieldType.SingleSelect); + }); + + testWidgets('check document exists in row detail page', (tester) async { await tester.initializeAppFlowy(); await tester.tapGoButton(); @@ -149,7 +182,7 @@ void main() { await tester.assertDocumentExistInRowDetailPage(); }); - testWidgets('update the content of the document and re-open it', + testWidgets('update the contents of the document and re-open it', (tester) async { await tester.initializeAppFlowy(); await tester.tapGoButton(); @@ -239,6 +272,7 @@ void main() { // Hover first row and then open the row page await tester.openFirstRowDetailPage(); + await tester.tapRowDetailPageRowActionButton(); await tester.tapRowDetailPageDeleteRowButton(); await tester.tapEscButton(); @@ -255,6 +289,7 @@ void main() { // Hover first row and then open the row page await tester.openFirstRowDetailPage(); + await tester.tapRowDetailPageRowActionButton(); await tester.tapRowDetailPageDuplicateRowButton(); await tester.tapEscButton(); diff --git a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart index db6433b51059..81ca2e4953b2 100644 --- a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart @@ -48,6 +48,7 @@ import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/row_property.dart'; import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.dart'; import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.dart'; @@ -485,7 +486,7 @@ extension AppFlowyDatabaseTest on WidgetTester { expect(banner, findsOneWidget); await startGesture( - getTopLeft(banner), + getCenter(banner), kind: PointerDeviceKind.mouse, ); @@ -524,6 +525,31 @@ extension AppFlowyDatabaseTest on WidgetTester { await tapButton(deleteButton); } + Future hoverOnFieldInRowDetail({required int index}) async { + final fieldButtons = find.byType(FieldCellButton); + final button = find + .descendant(of: find.byType(RowDetailPage), matching: fieldButtons) + .at(index); + return startGesture( + getCenter(button), + kind: PointerDeviceKind.mouse, + ); + } + + Future reorderFieldInRowDetail({required double offset}) async { + final thumb = find + .byWidgetPredicate( + (widget) => widget is ReorderableDragStartListener && widget.enabled, + ) + .first; + await drag( + thumb, + Offset(0, offset), + kind: PointerDeviceKind.mouse, + ); + await pumpAndSettle(); + } + Future scrollGridByOffset(Offset offset) async { await drag(find.byType(GridPage), offset); await pumpAndSettle(); @@ -601,6 +627,10 @@ extension AppFlowyDatabaseTest on WidgetTester { await tapButton(button); } + Future tapRowDetailPageRowActionButton() async { + await tapButton(find.byType(RowActionButton)); + } + Future tapRowDetailPageCreatePropertyButton() async { await tapButton(find.byType(CreateRowFieldButton)); } @@ -670,6 +700,18 @@ extension AppFlowyDatabaseTest on WidgetTester { expect(field, findsOneWidget); } + void assertFirstFieldInRowDetailByType(FieldType fieldType) { + final firstField = find + .descendant( + of: find.byType(RowDetailPage), + matching: find.byType(FieldCellButton), + ) + .first; + + final widget = this.widget(firstField); + expect(widget.field.fieldType, fieldType); + } + Future findFieldWithName(String name) async { final field = find.byWidgetPredicate( (widget) => widget is FieldCellButton && widget.field.name == name, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart index 2df72b84f466..940cf788b9f7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/database_controller.dart @@ -395,13 +395,7 @@ class RowDataBuilder { } void insertDate(FieldInfo fieldInfo, DateTime date) { - assert( - [ - FieldType.DateTime, - FieldType.LastEditedTime, - FieldType.CreatedTime, - ].contains(fieldInfo.fieldType), - ); + assert(FieldType.DateTime == fieldInfo.fieldType); final timestamp = date.millisecondsSinceEpoch ~/ 1000; _cellDataByFieldId[fieldInfo.field.id] = timestamp.toString(); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_banner_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_banner_bloc.dart index 3fc60ec7752a..e06bd21c217a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_banner_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_banner_bloc.dart @@ -32,11 +32,7 @@ class RowBannerBloc extends Bloc { await _listenRowMeteChanged(); }, didReceiveRowMeta: (RowMetaPB rowMeta) { - emit( - state.copyWith( - rowMeta: rowMeta, - ), - ); + emit(state.copyWith(rowMeta: rowMeta)); }, setCover: (String coverURL) { _updateMeta(coverURL: coverURL); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart index d37e3ac0262f..6023e3a89125 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart @@ -1,23 +1,20 @@ +import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_service.dart'; import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart'; -import 'package:appflowy/plugins/database_view/application/row/row_service.dart'; +import 'package:appflowy/plugins/database_view/application/row/row_controller.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'dart:async'; -import '../../../application/cell/cell_service.dart'; -import '../../../application/field/field_service.dart'; -import '../../../application/row/row_controller.dart'; + part 'row_detail_bloc.freezed.dart'; class RowDetailBloc extends Bloc { - final RowBackendService rowService; final RowController rowController; RowDetailBloc({ required this.rowController, - }) : rowService = RowBackendService(viewId: rowController.viewId), - super(RowDetailState.initial()) { + }) : super(RowDetailState.initial()) { on( (event, emit) async { await event.when( @@ -58,14 +55,8 @@ class RowDetailBloc extends Bloc { (err) => Log.error(err), ); }, - deleteRow: (rowId) async { - await rowService.deleteRow(rowId); - }, - duplicateRow: (String rowId, String? groupId) async { - await rowService.duplicateRow( - rowId: rowId, - groupId: groupId, - ); + reorderField: (fieldId, fromIndex, toIndex) async { + await _reorderField(fieldId, fromIndex, toIndex, emit); }, ); }, @@ -94,6 +85,25 @@ class RowDetailBloc extends Bloc { fieldId: fieldId, ); } + + Future _reorderField( + String fieldId, + int fromIndex, + int toIndex, + Emitter emit, + ) async { + final cells = List.from(state.cells); + cells.insert(toIndex, cells.removeAt(fromIndex)); + emit(state.copyWith(cells: cells)); + + final fieldService = + FieldBackendService(viewId: rowController.viewId, fieldId: fieldId); + final result = await fieldService.moveField( + fromIndex, + toIndex, + ); + result.fold((l) {}, (err) => Log.error(err)); + } } @freezed @@ -102,9 +112,11 @@ class RowDetailEvent with _$RowDetailEvent { const factory RowDetailEvent.deleteField(String fieldId) = _DeleteField; const factory RowDetailEvent.showField(String fieldId) = _ShowField; const factory RowDetailEvent.hideField(String fieldId) = _HideField; - const factory RowDetailEvent.deleteRow(String rowId) = _DeleteRow; - const factory RowDetailEvent.duplicateRow(String rowId, String? groupId) = - _DuplicateRow; + const factory RowDetailEvent.reorderField( + String fieldId, + int fromIndex, + int toIndex, + ) = _ReorderField; const factory RowDetailEvent.didReceiveCellDatas( List gridCells, ) = _DidReceiveCellDatas; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart index 8a232adf252a..737cae19cbe2 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart @@ -154,29 +154,33 @@ class FieldCellButton extends StatelessWidget { final FieldPB field; final int? maxLines; final BorderRadius? radius; + final EdgeInsets? margin; const FieldCellButton({ required this.field, required this.onTap, this.maxLines = 1, this.radius = BorderRadius.zero, + this.margin, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return FlowyButton( - hoverColor: AFThemeExtension.of(context).greyHover, + hoverColor: AFThemeExtension.of(context).lightGreyHover, onTap: onTap, leftIcon: FlowySvg( field.fieldType.icon(), + color: Theme.of(context).iconTheme.color, ), radius: radius, text: FlowyText.medium( field.name, maxLines: maxLines, overflow: TextOverflow.ellipsis, + color: AFThemeExtension.of(context).textColor, ), - margin: GridSize.cellContentInsets, + margin: margin ?? GridSize.cellContentInsets, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/action.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/action.dart index 9da9bc523a21..32c81341684f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/action.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/action.dart @@ -40,8 +40,7 @@ class RowActions extends StatelessWidget { .map((action) => _ActionCell(action: action)) .toList(); - // - final list = ListView.separated( + return ListView.separated( shrinkWrap: true, controller: ScrollController(), itemCount: cells.length, @@ -53,7 +52,6 @@ class RowActions extends StatelessWidget { return cells[index]; }, ); - return list; }, ), ); @@ -70,6 +68,7 @@ class _ActionCell extends StatelessWidget { height: GridSize.popoverItemHeight, child: FlowyButton( hoverColor: AFThemeExtension.of(context).lightGreyHover, + useIntrinsicWidth: true, text: FlowyText.medium( action.title(), color: action.enable() diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/accessory/cell_accessory.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/accessory/cell_accessory.dart index d756601bbda4..eb12292e0f66 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/accessory/cell_accessory.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/accessory/cell_accessory.dart @@ -1,9 +1,9 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -91,50 +91,31 @@ class _PrimaryCellAccessoryState extends State class AccessoryHover extends StatefulWidget { final CellAccessory child; - final EdgeInsets contentPadding; - const AccessoryHover({ - required this.child, - this.contentPadding = EdgeInsets.zero, - Key? key, - }) : super(key: key); + const AccessoryHover({required this.child, super.key}); @override State createState() => _AccessoryHoverState(); } class _AccessoryHoverState extends State { - late AccessoryHoverState _hoverState; - VoidCallback? _listenerFn; - - @override - void initState() { - _hoverState = AccessoryHoverState(); - _listenerFn = () => - _hoverState.onHover = widget.child.onAccessoryHover?.value ?? false; - widget.child.onAccessoryHover?.addListener(_listenerFn!); - - super.initState(); - } - - @override - void dispose() { - _hoverState.dispose(); - - if (_listenerFn != null) { - widget.child.onAccessoryHover?.removeListener(_listenerFn!); - _listenerFn = null; - } - super.dispose(); - } + bool _isHover = false; @override Widget build(BuildContext context) { final List children = [ - Padding(padding: widget.contentPadding, child: widget.child), + DecoratedBox( + decoration: BoxDecoration( + color: _isHover + ? AFThemeExtension.of(context).lightGreyHover + : Colors.transparent, + borderRadius: Corners.s6Border, + ), + child: widget.child, + ), ]; final accessoryBuilder = widget.child.accessoryBuilder; - if (accessoryBuilder != null) { + if (accessoryBuilder != null && _isHover) { final accessories = accessoryBuilder( (GridCellAccessoryBuildContext( anchorContext: context, @@ -149,36 +130,20 @@ class _AccessoryHoverState extends State { ); } - return ChangeNotifierProvider.value( - value: _hoverState, - child: MouseRegion( - cursor: SystemMouseCursors.click, - opaque: false, - onEnter: (p) => setState(() => _hoverState.onHover = true), - onExit: (p) => setState(() => _hoverState.onHover = false), - child: Stack( - fit: StackFit.loose, - alignment: AlignmentDirectional.center, - children: children, - ), + return MouseRegion( + cursor: SystemMouseCursors.click, + opaque: false, + onEnter: (p) => setState(() => _isHover = true), + onExit: (p) => setState(() => _isHover = false), + child: Stack( + fit: StackFit.loose, + alignment: AlignmentDirectional.center, + children: children, ), ); } } -class AccessoryHoverState extends ChangeNotifier { - bool _onHover = false; - - set onHover(bool value) { - if (_onHover != value) { - _onHover = value; - notifyListeners(); - } - } - - bool get onHover => _onHover; -} - class CellAccessoryContainer extends StatelessWidget { final List accessories; const CellAccessoryContainer({required this.accessories, Key? key}) diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart index d5a0fee160bb..e17a5f104ed1 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart @@ -35,6 +35,7 @@ class GridCellBuilder { case FieldType.Checkbox: return GridCheckboxCell( cellControllerBuilder: cellControllerBuilder, + style: style, key: key, ); case FieldType.DateTime: @@ -71,6 +72,7 @@ class GridCellBuilder { case FieldType.Number: return GridNumberCell( cellControllerBuilder: cellControllerBuilder, + style: style, key: key, ); case FieldType.RichText: diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell.dart index e064b39b9bd9..86dce6ca5415 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell.dart @@ -9,12 +9,29 @@ import 'checkbox_cell_bloc.dart'; import '../../../../grid/presentation/layout/sizes.dart'; import '../../cell_builder.dart'; +class GridCheckboxCellStyle extends GridCellStyle { + EdgeInsets? cellPadding; + + GridCheckboxCellStyle({ + this.cellPadding, + }); +} + class GridCheckboxCell extends GridCellWidget { final CellControllerBuilder cellControllerBuilder; + late final GridCheckboxCellStyle cellStyle; + GridCheckboxCell({ required this.cellControllerBuilder, + GridCellStyle? style, Key? key, - }) : super(key: key); + }) : super(key: key) { + if (style != null) { + cellStyle = (style as GridCheckboxCellStyle); + } else { + cellStyle = GridCheckboxCellStyle(); + } + } @override GridCellState createState() => _CheckboxCellState(); @@ -46,7 +63,8 @@ class _CheckboxCellState extends GridCellState { return Align( alignment: Alignment.centerLeft, child: Padding( - padding: GridSize.cellContentInsets, + padding: + widget.cellStyle.cellPadding ?? GridSize.cellContentInsets, child: FlowyIconButton( hoverColor: Colors.transparent, onPressed: () => context diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart index 1eb14193ce7e..041d5073e789 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart @@ -1,7 +1,8 @@ import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../grid/presentation/layout/sizes.dart'; @@ -10,9 +11,15 @@ import 'date_cell_bloc.dart'; import 'date_editor.dart'; class DateCellStyle extends GridCellStyle { + String? placeholder; Alignment alignment; + EdgeInsets? cellPadding; - DateCellStyle({this.alignment = Alignment.center}); + DateCellStyle({ + this.placeholder, + this.alignment = Alignment.center, + this.cellPadding, + }); } abstract class GridCellDelegate { @@ -71,7 +78,10 @@ class _DateCellState extends GridCellState { margin: EdgeInsets.zero, child: GridDateCellText( dateStr: state.dateStr, + placeholder: widget.cellStyle?.placeholder ?? "", alignment: alignment, + cellPadding: + widget.cellStyle?.cellPadding ?? GridSize.cellContentInsets, ), popupBuilder: (BuildContext popoverContent) { return DateCellEditor( @@ -107,24 +117,31 @@ class _DateCellState extends GridCellState { class GridDateCellText extends StatelessWidget { final String dateStr; + final String placeholder; final Alignment alignment; + final EdgeInsets cellPadding; const GridDateCellText({ required this.dateStr, + required this.placeholder, required this.alignment, + required this.cellPadding, super.key, }); @override Widget build(BuildContext context) { - return SizedBox.expand( - child: Align( - alignment: alignment, - child: Padding( - padding: GridSize.cellContentInsets, - child: FlowyText.medium( - dateStr, - maxLines: null, - ), + final isPlaceholder = dateStr.isEmpty; + final text = isPlaceholder ? placeholder : dateStr; + return Align( + alignment: alignment, + child: Padding( + padding: cellPadding, + child: FlowyText.medium( + text, + color: isPlaceholder + ? Theme.of(context).hintColor + : AFThemeExtension.of(context).textColor, + maxLines: null, ), ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell.dart index 3f00c76214aa..4dee176bbe32 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell.dart @@ -7,13 +7,33 @@ import 'number_cell_bloc.dart'; import '../../../../grid/presentation/layout/sizes.dart'; import '../../cell_builder.dart'; +class GridNumberCellStyle extends GridCellStyle { + String? placeholder; + TextStyle? textStyle; + EdgeInsets? cellPadding; + + GridNumberCellStyle({ + this.placeholder, + this.textStyle, + this.cellPadding, + }); +} + class GridNumberCell extends GridCellWidget { final CellControllerBuilder cellControllerBuilder; + late final GridNumberCellStyle cellStyle; GridNumberCell({ required this.cellControllerBuilder, - Key? key, - }) : super(key: key); + required GridCellStyle? style, + super.key, + }) { + if (style != null) { + cellStyle = (style as GridNumberCellStyle); + } else { + cellStyle = GridNumberCellStyle(); + } + } @override GridEditableTextCell createState() => _NumberCellState(); @@ -57,9 +77,10 @@ class _NumberCellState extends GridEditableTextCell { maxLines: null, style: Theme.of(context).textTheme.bodyMedium, textInputAction: TextInputAction.done, - decoration: const InputDecoration( + decoration: InputDecoration( contentPadding: EdgeInsets.zero, border: InputBorder.none, + hintText: widget.cellStyle.placeholder, isDense: true, ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart index 6e25de23b560..5b4ec4f037fd 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart @@ -93,7 +93,7 @@ class SelectOptionTag extends StatelessWidget { @override Widget build(BuildContext context) { EdgeInsets padding = - const EdgeInsets.symmetric(vertical: 2.0, horizontal: 8.0); + const EdgeInsets.symmetric(vertical: 1.5, horizontal: 8.0); if (onRemove != null) { padding = padding.copyWith(right: 2.0); } @@ -110,6 +110,7 @@ class SelectOptionTag extends StatelessWidget { Flexible( child: FlowyText.medium( name, + fontSize: FontSizes.s11, overflow: TextOverflow.ellipsis, color: AFThemeExtension.of(context).textColor, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell.dart index d965a55e86be..38f05212ee80 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell.dart @@ -13,9 +13,11 @@ import 'select_option_editor.dart'; class SelectOptionCellStyle extends GridCellStyle { String placeholder; + EdgeInsets? cellPadding; SelectOptionCellStyle({ required this.placeholder, + this.cellPadding, }); } @@ -170,10 +172,7 @@ class _SelectOptionWrapState extends State { final Widget child = _buildOptions(context); final constraints = BoxConstraints.loose( - Size( - SelectOptionCellEditor.editorPanelWidth, - 300, - ), + Size(SelectOptionCellEditor.editorPanelWidth, 300), ); return AppFlowyPopover( controller: widget.popoverController, @@ -191,7 +190,7 @@ class _SelectOptionWrapState extends State { }, onClose: () => widget.onCellEditing.value = false, child: Padding( - padding: GridSize.cellContentInsets, + padding: widget.cellStyle?.cellPadding ?? GridSize.cellContentInsets, child: child, ), ); @@ -200,9 +199,12 @@ class _SelectOptionWrapState extends State { Widget _buildOptions(BuildContext context) { final Widget child; if (widget.selectOptions.isEmpty && widget.cellStyle != null) { - child = FlowyText.medium( - widget.cellStyle!.placeholder, - color: Theme.of(context).hintColor, + child = Padding( + padding: const EdgeInsets.symmetric(vertical: 1), + child: FlowyText.medium( + widget.cellStyle!.placeholder, + color: Theme.of(context).hintColor, + ), ); } else { final children = widget.selectOptions.map( diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart index b74e23712cd9..efc03a6838e1 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart @@ -14,6 +14,7 @@ class GridTextCellStyle extends GridCellStyle { double emojiFontSize; double emojiHPadding; bool showEmoji; + EdgeInsets? cellPadding; GridTextCellStyle({ this.placeholder, @@ -22,6 +23,7 @@ class GridTextCellStyle extends GridCellStyle { this.showEmoji = true, this.emojiFontSize = 16, this.emojiHPadding = 0, + this.cellPadding, }); } @@ -72,10 +74,11 @@ class _GridTextCellState extends GridEditableTextCell { } }, child: Padding( - padding: EdgeInsets.only( - left: GridSize.cellContentInsets.left, - right: GridSize.cellContentInsets.right, - ), + padding: widget.cellStyle.cellPadding ?? + EdgeInsets.only( + left: GridSize.cellContentInsets.left, + right: GridSize.cellContentInsets.right, + ), child: Row( children: [ if (widget.cellStyle.showEmoji) diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/timestamp_cell/timestamp_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/timestamp_cell/timestamp_cell.dart index 7f155c6eae81..3dbcb3e194f9 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/timestamp_cell/timestamp_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/timestamp_cell/timestamp_cell.dart @@ -3,14 +3,21 @@ import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.da import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/timestamp_cell/timestamp_cell_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; +import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class TimestampCellStyle extends GridCellStyle { + String? placeholder; Alignment alignment; + EdgeInsets? cellPadding; - TimestampCellStyle({this.alignment = Alignment.center}); + TimestampCellStyle({ + this.placeholder, + this.alignment = Alignment.center, + this.cellPadding, + }); } class GridTimestampCell extends GridCellWidget { @@ -51,16 +58,28 @@ class _TimestampCellState extends GridCellState { @override Widget build(BuildContext context) { - final alignment = widget.cellStyle != null - ? widget.cellStyle!.alignment - : Alignment.centerLeft; + final alignment = widget.cellStyle?.alignment ?? Alignment.centerLeft; + final placeholder = widget.cellStyle?.placeholder ?? ""; + final padding = widget.cellStyle?.cellPadding ?? GridSize.cellContentInsets; + return BlocProvider.value( value: _cellBloc, child: BlocBuilder( builder: (context, state) { - return GridTimestampCellText( - dateStr: state.dateStr, + final isEmpty = state.dateStr.isEmpty; + final text = isEmpty ? placeholder : state.dateStr; + return Align( alignment: alignment, + child: Padding( + padding: padding, + child: FlowyText.medium( + text, + color: isEmpty + ? Theme.of(context).hintColor + : AFThemeExtension.of(context).textColor, + maxLines: null, + ), + ), ); }, ), @@ -81,29 +100,3 @@ class _TimestampCellState extends GridCellState { return; } } - -class GridTimestampCellText extends StatelessWidget { - final String dateStr; - final Alignment alignment; - const GridTimestampCellText({ - required this.dateStr, - required this.alignment, - super.key, - }); - - @override - Widget build(BuildContext context) { - return SizedBox.expand( - child: Align( - alignment: alignment, - child: Padding( - padding: GridSize.cellContentInsets, - child: FlowyText.medium( - dateStr, - maxLines: null, - ), - ), - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_action.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_action.dart index 15f04a2503d0..27b6404734cd 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_action.dart @@ -1,18 +1,10 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; -import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart'; import 'package:appflowy/plugins/database_view/application/row/row_controller.dart'; -import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart'; +import 'package:appflowy/plugins/database_view/grid/application/row/row_action_sheet_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; -import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -20,36 +12,39 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class RowActionList extends StatelessWidget { final RowController rowController; const RowActionList({ - required String viewId, required this.rowController, - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 10), - child: FlowyText(LocaleKeys.grid_row_action.tr()), - ), - const VSpace(15), - RowDetailPageDeleteButton(rowId: rowController.rowId), - RowDetailPageDuplicateButton( - rowId: rowController.rowId, - groupId: rowController.groupId, + return BlocProvider( + create: (context) => RowActionSheetBloc( + viewId: rowController.viewId, + rowId: rowController.rowId, + groupId: rowController.groupId, + ), + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + RowDetailPageDuplicateButton( + rowId: rowController.rowId, + groupId: rowController.groupId, + ), + const VSpace(4.0), + RowDetailPageDeleteButton(rowId: rowController.rowId), + ], ), - ], + ), ); } } class RowDetailPageDeleteButton extends StatelessWidget { final String rowId; - const RowDetailPageDeleteButton({required this.rowId, Key? key}) - : super(key: key); + const RowDetailPageDeleteButton({required this.rowId, super.key}); @override Widget build(BuildContext context) { @@ -59,7 +54,9 @@ class RowDetailPageDeleteButton extends StatelessWidget { text: FlowyText.regular(LocaleKeys.grid_row_delete.tr()), leftIcon: const FlowySvg(FlowySvgs.trash_m), onTap: () { - context.read().add(RowDetailEvent.deleteRow(rowId)); + context + .read() + .add(const RowActionSheetEvent.deleteRow()); FlowyOverlay.pop(context); }, ), @@ -73,8 +70,8 @@ class RowDetailPageDuplicateButton extends StatelessWidget { const RowDetailPageDuplicateButton({ required this.rowId, this.groupId, - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { @@ -85,91 +82,11 @@ class RowDetailPageDuplicateButton extends StatelessWidget { leftIcon: const FlowySvg(FlowySvgs.copy_s), onTap: () { context - .read() - .add(RowDetailEvent.duplicateRow(rowId, groupId)); + .read() + .add(const RowActionSheetEvent.duplicateRow()); FlowyOverlay.pop(context); }, ), ); } } - -class CreateRowFieldButton extends StatefulWidget { - final String viewId; - - const CreateRowFieldButton({ - required this.viewId, - Key? key, - }) : super(key: key); - - @override - State createState() => _CreateRowFieldButtonState(); -} - -class _CreateRowFieldButtonState extends State { - late PopoverController popoverController; - late TypeOptionPB typeOption; - - @override - void initState() { - popoverController = PopoverController(); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return AppFlowyPopover( - constraints: BoxConstraints.loose(const Size(240, 200)), - controller: popoverController, - direction: PopoverDirection.topWithLeftAligned, - triggerActions: PopoverTriggerFlags.none, - margin: EdgeInsets.zero, - child: SizedBox( - height: 40, - child: FlowyButton( - text: FlowyText.medium( - LocaleKeys.grid_field_newProperty.tr(), - color: AFThemeExtension.of(context).textColor, - ), - hoverColor: AFThemeExtension.of(context).lightGreyHover, - onTap: () async { - final result = await TypeOptionBackendService.createFieldTypeOption( - viewId: widget.viewId, - ); - result.fold( - (l) { - typeOption = l; - popoverController.show(); - }, - (r) => Log.error("Failed to create field type option: $r"), - ); - }, - leftIcon: FlowySvg( - FlowySvgs.add_m, - color: AFThemeExtension.of(context).textColor, - ), - ), - ), - popupBuilder: (BuildContext popOverContext) { - return FieldEditor( - viewId: widget.viewId, - typeOptionLoader: FieldTypeOptionLoader( - viewId: widget.viewId, - field: typeOption.field_2, - ), - onDeleted: (fieldId) { - popoverController.close(); - NavigatorAlertDialog( - title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), - confirm: () { - context - .read() - .add(RowDetailEvent.deleteField(fieldId)); - }, - ).show(context); - }, - ); - }, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_banner.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_banner.dart index ff922e83c4e5..e31b1e3291d5 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_banner.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_banner.dart @@ -1,23 +1,27 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; import 'package:appflowy/plugins/database_view/application/row/row_banner_bloc.dart'; +import 'package:appflowy/plugins/database_view/application/row/row_controller.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/emoji_picker/emoji_picker.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -typedef RowBannerCellBuilder = Widget Function(String fieldId); +import 'cell_builder.dart'; +import 'cells/cells.dart'; class RowBanner extends StatefulWidget { - final String viewId; - final RowMetaPB rowMeta; - final RowBannerCellBuilder cellBuilder; + final RowController rowController; + final GridCellBuilder cellBuilder; + const RowBanner({ - required this.viewId, - required this.rowMeta, + required this.rowController, required this.cellBuilder, super.key, }); @@ -34,25 +38,39 @@ class _RowBannerState extends State { Widget build(BuildContext context) { return BlocProvider( create: (context) => RowBannerBloc( - viewId: widget.viewId, - rowMeta: widget.rowMeta, + viewId: widget.rowController.viewId, + rowMeta: widget.rowController.rowMeta, )..add(const RowBannerEvent.initial()), child: MouseRegion( onEnter: (event) => _isHovering.value = true, onExit: (event) => _isHovering.value = false, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Stack( children: [ - SizedBox( - height: 30, - child: _BannerAction( - isHovering: _isHovering, - popoverController: popoverController, + Padding( + padding: const EdgeInsets.fromLTRB(60, 34, 60, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 30, + child: _BannerAction( + isHovering: _isHovering, + popoverController: popoverController, + ), + ), + const HSpace(4), + _BannerTitle( + cellBuilder: widget.cellBuilder, + popoverController: popoverController, + rowController: widget.rowController, + ), + ], ), ), - _BannerTitle( - cellBuilder: widget.cellBuilder, - popoverController: popoverController, + Positioned( + top: 12, + right: 12, + child: RowActionButton(rowController: widget.rowController), ), ], ), @@ -74,50 +92,54 @@ class _BannerAction extends StatelessWidget { return ValueListenableBuilder( valueListenable: isHovering, builder: (BuildContext context, bool value, Widget? child) { - if (value) { - return BlocBuilder( - builder: (context, state) { - final children = []; - final rowMeta = state.rowMeta; - if (rowMeta.icon.isEmpty) { - children.add( - EmojiPickerButton( - showEmojiPicker: () => popoverController.show(), - ), - ); - } else { - children.add( - RemoveEmojiButton( - onRemoved: () { - context - .read() - .add(const RowBannerEvent.setIcon('')); - }, - ), - ); - } - return Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: children, - ); - }, - ); - } else { + if (!value) { return const SizedBox(height: _kBannerActionHeight); } + + return BlocBuilder( + builder: (context, state) { + final children = []; + final rowMeta = state.rowMeta; + if (rowMeta.icon.isEmpty) { + children.add( + EmojiPickerButton( + showEmojiPicker: () => popoverController.show(), + ), + ); + } else { + children.add( + RemoveEmojiButton( + onRemoved: () { + context + .read() + .add(const RowBannerEvent.setIcon('')); + }, + ), + ); + } + return Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: children, + ); + }, + ); }, ); } } class _BannerTitle extends StatefulWidget { - final RowBannerCellBuilder cellBuilder; + final GridCellBuilder cellBuilder; final PopoverController popoverController; + final RowController rowController; + const _BannerTitle({ required this.cellBuilder, required this.popoverController, - }); + required this.rowController, + Key? key, + }) : super(key: key); @override State<_BannerTitle> createState() => _BannerTitleState(); @@ -139,10 +161,24 @@ class _BannerTitleState extends State<_BannerTitle> { ); } + children.add(const HSpace(4)); + if (state.primaryField != null) { + final style = GridTextCellStyle( + placeholder: LocaleKeys.grid_row_titlePlaceholder.tr(), + textStyle: Theme.of(context).textTheme.titleLarge, + showEmoji: false, + autofocus: true, + cellPadding: EdgeInsets.zero, + ); + final cellContext = DatabaseCellContext( + viewId: widget.rowController.viewId, + rowMeta: widget.rowController.rowMeta, + fieldInfo: FieldInfo.initial(state.primaryField!), + ); children.add( Expanded( - child: widget.cellBuilder(state.primaryField!.id), + child: widget.cellBuilder.build(cellContext, style: style), ), ); } @@ -211,16 +247,14 @@ class _EmojiPickerButtonState extends State { Widget build(BuildContext context) { return SizedBox( height: 26, - width: 160, child: FlowyButton( + useIntrinsicWidth: true, text: FlowyText.medium( LocaleKeys.document_plugins_cover_addIcon.tr(), ), - leftIcon: const Icon( - Icons.emoji_emotions, - size: 16, - ), + leftIcon: const FlowySvg(FlowySvgs.emoji_s), onTap: widget.showEmojiPicker, + margin: const EdgeInsets.all(4), ), ); } @@ -239,16 +273,14 @@ class RemoveEmojiButton extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( height: 26, - width: 160, child: FlowyButton( + useIntrinsicWidth: true, text: FlowyText.medium( LocaleKeys.document_plugins_cover_removeIcon.tr(), ), - leftIcon: const Icon( - Icons.emoji_emotions, - size: 16, - ), + leftIcon: const FlowySvg(FlowySvgs.emoji_s), onTap: onRemoved, + margin: const EdgeInsets.all(4), ), ); } @@ -263,3 +295,22 @@ Widget _buildEmojiPicker(OnSubmittedEmoji onSubmitted) { ), ); } + +class RowActionButton extends StatelessWidget { + final RowController rowController; + const RowActionButton({super.key, required this.rowController}); + + @override + Widget build(BuildContext context) { + return AppFlowyPopover( + direction: PopoverDirection.bottomWithLeftAligned, + popupBuilder: (context) => RowActionList(rowController: rowController), + child: FlowyIconButton( + width: 20, + height: 20, + icon: const FlowySvg(FlowySvgs.details_horizontal_s), + iconColorOnHover: Theme.of(context).colorScheme.onSecondary, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart index ac6cca138825..4ab52f899e0b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart @@ -1,18 +1,12 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/row/row_controller.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart'; import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart'; -import 'package:collection/collection.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'cell_builder.dart'; -import 'cells/text_cell/text_cell.dart'; -import 'row_action.dart'; import 'row_banner.dart'; import 'row_property.dart'; @@ -47,17 +41,29 @@ class _RowDetailPageState extends State { Widget build(BuildContext context) { return FlowyDialog( child: BlocProvider( - create: (context) { - return RowDetailBloc(rowController: widget.rowController) - ..add(const RowDetailEvent.initial()); - }, + create: (context) => RowDetailBloc(rowController: widget.rowController) + ..add(const RowDetailEvent.initial()), child: ListView( controller: scrollController, children: [ - _rowBanner(), - IntrinsicHeight(child: _responsiveRowInfo()), - const Divider(height: 1.0), - const VSpace(10), + RowBanner( + rowController: widget.rowController, + cellBuilder: widget.cellBuilder, + ), + const VSpace(16), + Padding( + padding: const EdgeInsets.only(left: 40, right: 60), + child: RowPropertyList( + cellBuilder: widget.cellBuilder, + viewId: widget.rowController.viewId, + ), + ), + const VSpace(20), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 60), + child: Divider(height: 1.0), + ), + const VSpace(20), RowDocument( viewId: widget.rowController.viewId, rowId: widget.rowController.rowId, @@ -68,104 +74,4 @@ class _RowDetailPageState extends State { ), ); } - - Widget _rowBanner() { - return BlocBuilder( - builder: (context, state) { - final paddingOffset = getHorizontalPadding(context); - return Padding( - padding: EdgeInsets.only( - left: paddingOffset, - right: paddingOffset, - top: 20, - ), - child: RowBanner( - rowMeta: widget.rowController.rowMeta, - viewId: widget.rowController.viewId, - cellBuilder: (fieldId) { - final fieldInfo = state.cells - .firstWhereOrNull( - (e) => e.fieldInfo.field.id == fieldId, - ) - ?.fieldInfo; - - if (fieldInfo != null) { - final style = GridTextCellStyle( - placeholder: LocaleKeys.grid_row_titlePlaceholder.tr(), - textStyle: Theme.of(context).textTheme.titleLarge, - showEmoji: false, - autofocus: true, - ); - final cellContext = DatabaseCellContext( - viewId: widget.rowController.viewId, - rowMeta: widget.rowController.rowMeta, - fieldInfo: fieldInfo, - ); - return widget.cellBuilder.build(cellContext, style: style); - } else { - return const SizedBox.shrink(); - } - }, - ), - ); - }, - ); - } - - Widget _responsiveRowInfo() { - final rowDataColumn = RowPropertyList( - cellBuilder: widget.cellBuilder, - viewId: widget.rowController.viewId, - ); - final rowOptionColumn = RowActionList( - viewId: widget.rowController.viewId, - rowController: widget.rowController, - ); - final paddingOffset = getHorizontalPadding(context); - if (MediaQuery.of(context).size.width > 800) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - flex: 3, - child: Padding( - padding: EdgeInsets.fromLTRB(paddingOffset, 0, 20, 20), - child: rowDataColumn, - ), - ), - const VerticalDivider(width: 1.0), - Flexible( - child: Padding( - padding: EdgeInsets.fromLTRB(20, 0, paddingOffset, 0), - child: rowOptionColumn, - ), - ), - ], - ); - } else { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.fromLTRB(paddingOffset, 0, 20, 20), - child: rowDataColumn, - ), - const Divider(height: 1.0), - Padding( - padding: EdgeInsets.symmetric(horizontal: paddingOffset), - child: rowOptionColumn, - ) - ], - ); - } - } -} - -double getHorizontalPadding(BuildContext context) { - if (MediaQuery.of(context).size.width > 800) { - return 50; - } else { - return 20; - } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_property.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_property.dart index e3dd7f3f0495..ef73c7406201 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_property.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_property.dart @@ -1,21 +1,27 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart'; import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; +import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart'; -import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'accessory/cell_accessory.dart'; import 'cell_builder.dart'; +import 'cells/checkbox_cell/checkbox_cell.dart'; import 'cells/date_cell/date_cell.dart'; +import 'cells/number_cell/number_cell.dart'; import 'cells/select_option_cell/select_option_cell.dart'; import 'cells/text_cell/text_cell.dart'; import 'cells/timestamp_cell/timestamp_cell.dart'; @@ -23,7 +29,6 @@ import 'cells/url_cell/url_cell.dart'; /// Display the row properties in a list. Only use this widget in the /// [RowDetailPage]. -/// class RowPropertyList extends StatelessWidget { final String viewId; final GridCellBuilder cellBuilder; @@ -38,39 +43,88 @@ class RowPropertyList extends StatelessWidget { return BlocBuilder( buildWhen: (previous, current) => previous.cells != current.cells, builder: (context, state) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // The rest of the fields are displayed in the order of the field - // list - ...state.cells - .where((element) => !element.fieldInfo.field.isPrimary) - .map( - (cell) => _PropertyCell( - cellContext: cell, - cellBuilder: cellBuilder, - ), - ) - .toList(), - const VSpace(20), - - // Create a new property(field) button - CreateRowFieldButton(viewId: viewId), - ], + final children = state.cells + .where((element) => !element.fieldInfo.field.isPrimary) + .mapIndexed( + (index, cell) => _PropertyCell( + key: ValueKey('row_detail_${cell.fieldId}'), + cellContext: cell, + cellBuilder: cellBuilder, + index: index, + ), + ) + .toList(); + return ReorderableListView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + onReorder: (oldIndex, newIndex) { + final reorderedField = children[oldIndex].cellContext.fieldId; + _reorderField( + context, + state.cells, + reorderedField, + oldIndex, + newIndex, + ); + }, + buildDefaultDragHandles: false, + proxyDecorator: (child, index, animation) => Material( + color: Colors.transparent, + child: Stack( + children: [ + child, + const MouseRegion(cursor: SystemMouseCursors.grabbing), + ], + ), + ), + footer: Padding( + padding: const EdgeInsets.only(left: 20), + child: CreateRowFieldButton(viewId: viewId), + ), + children: children, ); }, ); } + + void _reorderField( + BuildContext context, + List cells, + String reorderedFieldId, + int oldIndex, + int newIndex, + ) { + // when reorderiing downwards, need to update index + if (oldIndex < newIndex) { + newIndex--; + } + + // also update index when the index is after the index of the primary field + // in the original list of DatabaseCellContext's + final primaryFieldIndex = + cells.indexWhere((element) => element.fieldInfo.isPrimary); + if (oldIndex >= primaryFieldIndex) { + oldIndex++; + } + if (newIndex >= primaryFieldIndex) { + newIndex++; + } + + context.read().add( + RowDetailEvent.reorderField(reorderedFieldId, oldIndex, newIndex), + ); + } } class _PropertyCell extends StatefulWidget { final DatabaseCellContext cellContext; final GridCellBuilder cellBuilder; + final int index; const _PropertyCell({ required this.cellContext, required this.cellBuilder, Key? key, + required this.index, }) : super(key: key); @override @@ -78,45 +132,65 @@ class _PropertyCell extends StatefulWidget { } class _PropertyCellState extends State<_PropertyCell> { - final PopoverController popover = PopoverController(); + final PopoverController _popoverController = PopoverController(); + bool _isFieldHover = false; @override Widget build(BuildContext context) { final style = _customCellStyle(widget.cellContext.fieldType); final cell = widget.cellBuilder.build(widget.cellContext, style: style); + final dragThumb = MouseRegion( + cursor: SystemMouseCursors.grab, + child: SizedBox( + width: 16, + height: 30, + child: _isFieldHover ? const FlowySvg(FlowySvgs.drag_element_s) : null, + ), + ); + final gesture = GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => cell.requestFocus.notify(), - child: AccessoryHover( - contentPadding: const EdgeInsets.symmetric(horizontal: 3, vertical: 3), - child: cell, - ), + child: AccessoryHover(child: cell), ); - return IntrinsicHeight( - child: ConstrainedBox( - constraints: const BoxConstraints(minHeight: 30), + return Container( + margin: const EdgeInsets.only(bottom: 8), + constraints: const BoxConstraints(minHeight: 30), + child: MouseRegion( + onEnter: (event) => setState(() => _isFieldHover = true), + onExit: (event) => setState(() => _isFieldHover = false), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ + ReorderableDragStartListener( + index: widget.index, + enabled: _isFieldHover, + child: dragThumb, + ), + const HSpace(4), AppFlowyPopover( - controller: popover, + controller: _popoverController, constraints: BoxConstraints.loose(const Size(240, 600)), margin: EdgeInsets.zero, triggerActions: PopoverTriggerFlags.none, + direction: PopoverDirection.bottomWithLeftAligned, popupBuilder: (popoverContext) => buildFieldEditor(), child: SizedBox( - width: 150, - height: 40, + width: 160, + height: 30, child: FieldCellButton( field: widget.cellContext.fieldInfo.field, - onTap: () => popover.show(), + onTap: () => _popoverController.show(), radius: BorderRadius.circular(6), + margin: + const EdgeInsets.symmetric(horizontal: 4, vertical: 6), ), ), ), + const HSpace(8), Expanded(child: gesture), ], ), @@ -133,11 +207,11 @@ class _PropertyCellState extends State<_PropertyCell> { field: widget.cellContext.fieldInfo.field, ), onHidden: (fieldId) { - popover.close(); + _popoverController.close(); context.read().add(RowDetailEvent.hideField(fieldId)); }, onDeleted: (fieldId) { - popover.close(); + _popoverController.close(); NavigatorAlertDialog( title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), @@ -155,26 +229,35 @@ class _PropertyCellState extends State<_PropertyCell> { GridCellStyle? _customCellStyle(FieldType fieldType) { switch (fieldType) { case FieldType.Checkbox: - return null; + return GridCheckboxCellStyle( + cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), + ); case FieldType.DateTime: return DateCellStyle( + placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), alignment: Alignment.centerLeft, + cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), ); case FieldType.LastEditedTime: case FieldType.CreatedTime: return TimestampCellStyle( + placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), alignment: Alignment.centerLeft, + cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), ); case FieldType.MultiSelect: return SelectOptionCellStyle( placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), + cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), ); case FieldType.Checklist: return SelectOptionCellStyle( placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), ); case FieldType.Number: - return null; + return GridNumberCellStyle( + placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), + ); case FieldType.RichText: return GridTextCellStyle( placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), @@ -182,6 +265,7 @@ GridCellStyle? _customCellStyle(FieldType fieldType) { case FieldType.SingleSelect: return SelectOptionCellStyle( placeholder: LocaleKeys.grid_row_textPlaceholder.tr(), + cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), ); case FieldType.URL: @@ -195,3 +279,81 @@ GridCellStyle? _customCellStyle(FieldType fieldType) { } throw UnimplementedError; } + +class CreateRowFieldButton extends StatefulWidget { + final String viewId; + + const CreateRowFieldButton({required this.viewId, super.key}); + + @override + State createState() => _CreateRowFieldButtonState(); +} + +class _CreateRowFieldButtonState extends State { + late PopoverController popoverController; + late TypeOptionPB typeOption; + + @override + void initState() { + popoverController = PopoverController(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return AppFlowyPopover( + constraints: BoxConstraints.loose(const Size(240, 200)), + controller: popoverController, + direction: PopoverDirection.topWithLeftAligned, + triggerActions: PopoverTriggerFlags.none, + margin: EdgeInsets.zero, + child: SizedBox( + height: 30, + child: FlowyButton( + margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 6), + text: FlowyText.medium( + LocaleKeys.grid_field_newProperty.tr(), + color: Theme.of(context).hintColor, + ), + hoverColor: AFThemeExtension.of(context).lightGreyHover, + onTap: () async { + final result = await TypeOptionBackendService.createFieldTypeOption( + viewId: widget.viewId, + ); + result.fold( + (l) { + typeOption = l; + popoverController.show(); + }, + (r) => Log.error("Failed to create field type option: $r"), + ); + }, + leftIcon: FlowySvg( + FlowySvgs.add_m, + color: Theme.of(context).hintColor, + ), + ), + ), + popupBuilder: (BuildContext popOverContext) { + return FieldEditor( + viewId: widget.viewId, + typeOptionLoader: FieldTypeOptionLoader( + viewId: widget.viewId, + field: typeOption.field_2, + ), + onDeleted: (fieldId) { + popoverController.close(); + NavigatorAlertDialog( + title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(), + confirm: () { + context + .read() + .add(RowDetailEvent.deleteField(fieldId)); + }, + ).show(context); + }, + ); + }, + ); + } +} diff --git a/frontend/resources/flowy_icons/16x/details_horizontal.svg b/frontend/resources/flowy_icons/16x/details_horizontal.svg new file mode 100644 index 000000000000..1d3d07b9156b --- /dev/null +++ b/frontend/resources/flowy_icons/16x/details_horizontal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/resources/flowy_icons/16x/emoji.svg b/frontend/resources/flowy_icons/16x/emoji.svg new file mode 100644 index 000000000000..b5fda7ea7447 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/emoji.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + From a4681a404259f95dce26acb0f4f2c1654aa1bc90 Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Wed, 13 Sep 2023 19:11:46 +0800 Subject: [PATCH 04/14] fix: don't use timestamp fields to make calendar (#3366) --- .../flowy-database2/src/entities/field_entities.rs | 2 -- .../flowy-database2/src/services/cell/type_cell_data.rs | 2 -- .../src/services/database_view/layout_deps.rs | 2 +- .../number_type_option/number_type_option.rs | 5 ++++- .../timestamp_type_option/timestamp_type_option.rs | 9 +++------ 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/frontend/rust-lib/flowy-database2/src/entities/field_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/field_entities.rs index f2ece71435fd..5d25b290f192 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/field_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/field_entities.rs @@ -558,8 +558,6 @@ impl FieldType { pub fn is_date(&self) -> bool { matches!(self, FieldType::DateTime) - || matches!(self, FieldType::LastEditedTime) - || matches!(self, FieldType::CreatedTime) } pub fn is_single_select(&self) -> bool { diff --git a/frontend/rust-lib/flowy-database2/src/services/cell/type_cell_data.rs b/frontend/rust-lib/flowy-database2/src/services/cell/type_cell_data.rs index 366ab9b8adce..07ebd91bf1b8 100644 --- a/frontend/rust-lib/flowy-database2/src/services/cell/type_cell_data.rs +++ b/frontend/rust-lib/flowy-database2/src/services/cell/type_cell_data.rs @@ -82,8 +82,6 @@ impl TypeCellData { pub fn is_date(&self) -> bool { self.field_type == FieldType::DateTime - || self.field_type == FieldType::LastEditedTime - || self.field_type == FieldType::CreatedTime } pub fn is_single_select(&self) -> bool { diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/layout_deps.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/layout_deps.rs index 83f9c28c8f5d..4171be414696 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/layout_deps.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/layout_deps.rs @@ -51,7 +51,7 @@ impl DatabaseLayoutDepsResolver { .lock() .get_fields(None) .into_iter() - .find(|field| FieldType::from(field.field_type).is_date()) + .find(|field| FieldType::from(field.field_type) == FieldType::DateTime) { Some(field) => { let layout_setting = CalendarLayoutSetting::new(field.id).into(); diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs index 91afbdca93ea..0ee75faed71c 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs @@ -184,7 +184,10 @@ impl CellDataDecoder for NumberTypeOption { decoded_field_type: &FieldType, _field: &Field, ) -> FlowyResult<::CellData> { - if decoded_field_type.is_date() { + if decoded_field_type.is_date() + || decoded_field_type.is_created_time() + || decoded_field_type.is_last_edited_time() + { return Ok(Default::default()); } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs index c27816052f64..30aacb4b55d7 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs @@ -130,11 +130,8 @@ impl CellDataDecoder for TimestampTypeOption { decoded_field_type: &FieldType, _field: &Field, ) -> FlowyResult<::CellData> { - // Return default data if the type_option_cell_data is not FieldType::DateTime. - // It happens when switching from one field to another. - // For example: - // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen. - if !decoded_field_type.is_date() { + // Return default data if the type_option_cell_data is not FieldType::CreatedTime nor FieldType::LastEditedTime + if !decoded_field_type.is_last_edited_time() && !decoded_field_type.is_created_time() { return Ok(Default::default()); } @@ -177,7 +174,7 @@ impl TypeOptionCellDataFilter for TimestampTypeOption { field_type: &FieldType, cell_data: &::CellData, ) -> bool { - if !field_type.is_date() { + if !field_type.is_last_edited_time() && !field_type.is_created_time() { return true; } From 50a4f0393106983957d29fb3a29314e78b3aa163 Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Wed, 13 Sep 2023 19:12:26 +0800 Subject: [PATCH 05/14] chore: date editor ui polish (#3367) --- .../row/cells/date_cell/date_editor.dart | 210 +++++++++--------- .../lib/style_widget/button.dart | 2 +- .../lib/style_widget/text_field.dart | 16 +- 3 files changed, 118 insertions(+), 110 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart index 1668f7b26601..f7088e1731de 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart @@ -10,7 +10,6 @@ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:dartz/dartz.dart' show Either; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/time/duration.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -51,7 +50,7 @@ class _DateCellEditor extends State { if (snapshot.hasData) { return _buildWidget(snapshot); } else { - return const SizedBox(); + return const SizedBox.shrink(); } }, ); @@ -67,7 +66,7 @@ class _DateCellEditor extends State { }, (err) { Log.error(err); - return const SizedBox(); + return const SizedBox.shrink(); }, ); } @@ -108,10 +107,6 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> { child: BlocBuilder( builder: (context, state) { final List children = [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: _buildCalendar(context), - ), AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: state.includeTime @@ -121,11 +116,13 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> { ) : const SizedBox.shrink(), ), - const TypeOptionSeparator(spacing: 12.0), + const DatePicker(), + const VSpace(8.0), + const TypeOptionSeparator(spacing: 8.0), const _IncludeTimeButton(), - const TypeOptionSeparator(spacing: 12.0), + const TypeOptionSeparator(spacing: 8.0), DateTypeOptionButton(popoverMutex: popoverMutex), - const TypeOptionSeparator(spacing: 12.0), + VSpace(GridSize.typeOptionSeparatorHeight), const ClearDateButton(), ]; @@ -134,7 +131,7 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> { controller: ScrollController(), itemCount: children.length, itemBuilder: (BuildContext context, int index) => children[index], - padding: const EdgeInsets.symmetric(vertical: 12.0), + padding: const EdgeInsets.only(top: 18.0, bottom: 12.0), ); }, ), @@ -146,84 +143,103 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> { popoverMutex.dispose(); super.dispose(); } +} - Widget _buildCalendar(BuildContext context) { +class DatePicker extends StatelessWidget { + const DatePicker({super.key}); + + @override + Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { final textStyle = Theme.of(context).textTheme.bodyMedium!; final boxDecoration = BoxDecoration( color: Theme.of(context).colorScheme.surface, - shape: BoxShape.rectangle, - borderRadius: Corners.s6Border, + shape: BoxShape.circle, ); - return TableCalendar( - firstDay: kFirstDay, - lastDay: kLastDay, - focusedDay: state.focusedDay, - rowHeight: GridSize.popoverItemHeight, - calendarFormat: state.format, - daysOfWeekHeight: GridSize.popoverItemHeight, - headerStyle: HeaderStyle( - formatButtonVisible: false, - titleCentered: true, - titleTextStyle: textStyle, - leftChevronMargin: EdgeInsets.zero, - leftChevronPadding: EdgeInsets.zero, - leftChevronIcon: FlowySvg( - FlowySvgs.arrow_left_s, - color: Theme.of(context).iconTheme.color, - ), - rightChevronPadding: EdgeInsets.zero, - rightChevronMargin: EdgeInsets.zero, - rightChevronIcon: FlowySvg( - FlowySvgs.arrow_right_s, - color: Theme.of(context).iconTheme.color, - ), - headerMargin: const EdgeInsets.only(bottom: 8.0), - ), - daysOfWeekStyle: DaysOfWeekStyle( - dowTextFormatter: (date, locale) => - DateFormat.E(locale).format(date).toUpperCase(), - weekdayStyle: AFThemeExtension.of(context).caption, - weekendStyle: AFThemeExtension.of(context).caption, - ), - calendarStyle: CalendarStyle( - cellMargin: const EdgeInsets.all(3), - defaultDecoration: boxDecoration, - selectedDecoration: boxDecoration.copyWith( - color: Theme.of(context).colorScheme.primary, - ), - todayDecoration: boxDecoration.copyWith( - color: AFThemeExtension.of(context).lightGreyHover, + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: TableCalendar( + firstDay: kFirstDay, + lastDay: kLastDay, + focusedDay: state.focusedDay, + rowHeight: 26.0 + 7.0, + calendarFormat: state.format, + daysOfWeekHeight: 17.0 + 8.0, + headerStyle: HeaderStyle( + formatButtonVisible: false, + titleCentered: true, + titleTextStyle: textStyle, + leftChevronMargin: EdgeInsets.zero, + leftChevronPadding: EdgeInsets.zero, + leftChevronIcon: FlowySvg( + FlowySvgs.arrow_left_s, + color: Theme.of(context).iconTheme.color, + ), + rightChevronPadding: EdgeInsets.zero, + rightChevronMargin: EdgeInsets.zero, + rightChevronIcon: FlowySvg( + FlowySvgs.arrow_right_s, + color: Theme.of(context).iconTheme.color, + ), + headerMargin: EdgeInsets.zero, + headerPadding: const EdgeInsets.only(bottom: 8.0), ), - weekendDecoration: boxDecoration, - outsideDecoration: boxDecoration, - defaultTextStyle: textStyle, - weekendTextStyle: textStyle, - selectedTextStyle: textStyle.copyWith( - color: Theme.of(context).colorScheme.surface, + calendarStyle: CalendarStyle( + cellMargin: const EdgeInsets.all(3.5), + defaultDecoration: boxDecoration, + selectedDecoration: boxDecoration.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + todayDecoration: boxDecoration.copyWith( + color: Colors.transparent, + border: + Border.all(color: Theme.of(context).colorScheme.primary), + ), + weekendDecoration: boxDecoration, + outsideDecoration: boxDecoration, + defaultTextStyle: textStyle, + weekendTextStyle: textStyle, + selectedTextStyle: textStyle.copyWith( + color: Theme.of(context).colorScheme.surface, + ), + todayTextStyle: textStyle, + outsideTextStyle: textStyle.copyWith( + color: Theme.of(context).disabledColor, + ), ), - todayTextStyle: textStyle, - outsideTextStyle: textStyle.copyWith( - color: Theme.of(context).disabledColor, + calendarBuilders: CalendarBuilders( + dowBuilder: (context, day) { + final locale = context.locale.toLanguageTag(); + final label = DateFormat.E(locale).format(day).substring(0, 2); + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Center( + child: Text( + label, + style: AFThemeExtension.of(context).caption, + ), + ), + ); + }, ), + selectedDayPredicate: (day) => isSameDay(state.dateTime, day), + onDaySelected: (selectedDay, focusedDay) { + context.read().add( + DateCellCalendarEvent.selectDay(selectedDay.toLocal().date), + ); + }, + onFormatChanged: (format) { + context + .read() + .add(DateCellCalendarEvent.setCalFormat(format)); + }, + onPageChanged: (focusedDay) { + context + .read() + .add(DateCellCalendarEvent.setFocusedDay(focusedDay)); + }, ), - selectedDayPredicate: (day) => isSameDay(state.dateTime, day), - onDaySelected: (selectedDay, focusedDay) { - context.read().add( - DateCellCalendarEvent.selectDay(selectedDay.toLocal().date), - ); - }, - onFormatChanged: (format) { - context - .read() - .add(DateCellCalendarEvent.setCalFormat(format)); - }, - onPageChanged: (focusedDay) { - context - .read() - .add(DateCellCalendarEvent.setFocusedDay(focusedDay)); - }, ); }, ); @@ -295,26 +311,21 @@ class _TimeTextFieldState extends State<_TimeTextField> { return BlocConsumer( listener: (context, state) => _textController.text = state.time ?? "", builder: (context, state) { - return Column( - children: [ - const VSpace(12), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: FlowyTextField( - text: state.time ?? "", - focusNode: _focusNode, - controller: _textController, - submitOnLeave: true, - hintText: state.timeHintText, - errorText: state.timeFormatError, - onSubmitted: (timeStr) { - context - .read() - .add(DateCellCalendarEvent.setTime(timeStr)); - }, - ), - ), - ], + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 18.0), + child: FlowyTextField( + text: state.time ?? "", + focusNode: _focusNode, + controller: _textController, + submitOnLeave: true, + hintText: state.timeHintText, + errorText: state.timeFormatError, + onSubmitted: (timeStr) { + context + .read() + .add(DateCellCalendarEvent.setTime(timeStr)); + }, + ), ); }, ); @@ -349,7 +360,6 @@ class DateTypeOptionButton extends StatelessWidget { height: GridSize.popoverItemHeight, child: FlowyButton( text: FlowyText.medium(title), - margin: GridSize.typeOptionContentInsets, rightIcon: const FlowySvg(FlowySvgs.more_s), ), ), @@ -461,8 +471,6 @@ class ClearDateButton extends StatelessWidget { .add(const DateCellCalendarEvent.clearDate()); PopoverContainer.of(context).close(); }, - leftIcon: const FlowySvg(FlowySvgs.delete_s), - margin: GridSize.typeOptionContentInsets, ), ), ); diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart index 86f891d5c8ed..a11ffe9879a3 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart @@ -100,7 +100,7 @@ class FlowyButton extends StatelessWidget { decoration: decoration, child: Padding( padding: - margin ?? const EdgeInsets.symmetric(horizontal: 10, vertical: 2), + margin ?? const EdgeInsets.symmetric(horizontal: 6, vertical: 4), child: child, ), ); diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_field.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_field.dart index 562f1f403c20..7bf05d64e0ba 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_field.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_field.dart @@ -105,18 +105,18 @@ class FlowyTextFieldState extends State { maxLines: widget.maxLines, maxLength: widget.maxLength, maxLengthEnforcement: MaxLengthEnforcement.truncateAfterCompositionEnds, - style: Theme.of(context).textTheme.bodyMedium, + style: Theme.of(context).textTheme.bodySmall, decoration: InputDecoration( - contentPadding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 13), + constraints: const BoxConstraints(maxHeight: 32), + contentPadding: const EdgeInsets.symmetric(horizontal: 12), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Theme.of(context).colorScheme.outline, width: 1.0, ), - borderRadius: Corners.s10Border, + borderRadius: Corners.s8Border, ), - isDense: true, + isDense: false, hintText: widget.hintText, errorText: widget.errorText, hintStyle: Theme.of(context) @@ -130,21 +130,21 @@ class FlowyTextFieldState extends State { color: Theme.of(context).colorScheme.primary, width: 1.0, ), - borderRadius: Corners.s10Border, + borderRadius: Corners.s8Border, ), errorBorder: OutlineInputBorder( borderSide: BorderSide( color: Theme.of(context).colorScheme.error, width: 1.0, ), - borderRadius: Corners.s10Border, + borderRadius: Corners.s8Border, ), focusedErrorBorder: OutlineInputBorder( borderSide: BorderSide( color: Theme.of(context).colorScheme.error, width: 1.0, ), - borderRadius: Corners.s10Border, + borderRadius: Corners.s8Border, ), ), ); From 524efc2620565c70b29dce779158877431d919c7 Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Wed, 13 Sep 2023 19:12:40 +0800 Subject: [PATCH 06/14] test: fi convert text to date cell test (#3377) --- .../services/field/type_options/text_type_option/text_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_tests.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_tests.rs index cccc5f84ab7d..001c67e61274 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_tests.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_tests.rs @@ -12,7 +12,7 @@ mod tests { #[test] fn date_type_to_text_type() { let field_type = FieldType::DateTime; - let field = FieldBuilder::from_field_type(field_type.clone()).build(); + let field = FieldBuilder::new(field_type.clone(), DateTypeOption::test()).build(); assert_eq!( stringify_cell_data( From 0c6a1d4ae764c27f55ce0f5dd1638afcdffe77db Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Wed, 13 Sep 2023 20:44:04 +0800 Subject: [PATCH 07/14] chore: revamp checklist ui (#3380) * chore: revamp checklist editor ui * chore: checklist progress bar * test: integration tests * fix: flutter analyzer errors * fix: checklist percentage complete --- .../integration_test/database_cell_test.dart | 112 +++++ .../integration_test/database_share_test.dart | 20 +- .../util/database_test_op.dart | 151 +++++- .../cell/checklist_cell_service.dart | 10 +- .../card/cells/checklist_card_cell.dart | 14 +- .../cells/checklist_cell/checklist_cell.dart | 10 +- .../checklist_cell/checklist_cell_bloc.dart | 28 +- .../checklist_cell/checklist_cell_editor.dart | 446 +++++++++++++----- .../checklist_cell_editor_bloc.dart | 90 ++-- .../checklist_progress_bar.dart | 135 +----- .../appflowy_popover/lib/src/mask.dart | 40 +- .../appflowy_popover/lib/src/popover.dart | 35 +- frontend/resources/translations/ar-SA.json | 2 +- frontend/resources/translations/ca-ES.json | 2 +- frontend/resources/translations/de-DE.json | 2 +- frontend/resources/translations/en.json | 4 +- frontend/resources/translations/es-VE.json | 2 +- frontend/resources/translations/eu-ES.json | 2 +- frontend/resources/translations/fa.json | 2 +- frontend/resources/translations/fr-CA.json | 2 +- frontend/resources/translations/fr-FR.json | 2 +- frontend/resources/translations/hu-HU.json | 2 +- frontend/resources/translations/id-ID.json | 2 +- frontend/resources/translations/it-IT.json | 2 +- frontend/resources/translations/ja-JP.json | 2 +- frontend/resources/translations/ko-KR.json | 2 +- frontend/resources/translations/pl-PL.json | 2 +- frontend/resources/translations/pt-BR.json | 2 +- frontend/resources/translations/pt-PT.json | 2 +- frontend/resources/translations/ru-RU.json | 2 +- frontend/resources/translations/sv.json | 2 +- frontend/resources/translations/tr-TR.json | 2 +- frontend/resources/translations/zh-CN.json | 2 +- frontend/resources/translations/zh-TW.json | 2 +- .../checklist_entities.rs | 2 +- .../tests/database/local_test/test.rs | 2 +- 36 files changed, 747 insertions(+), 394 deletions(-) diff --git a/frontend/appflowy_flutter/integration_test/database_cell_test.dart b/frontend/appflowy_flutter/integration_test/database_cell_test.dart index e9862db20804..63b30f5c1875 100644 --- a/frontend/appflowy_flutter/integration_test/database_cell_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_cell_test.dart @@ -437,4 +437,116 @@ void main() { await tester.pumpAndSettle(); }); }); + + testWidgets('edit checklist cell', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapGoButton(); + + await tester.createNewPageWithName(layout: ViewLayoutPB.Grid); + + const fieldType = FieldType.Checklist; + await tester.createField(fieldType, fieldType.name); + + // assert that there is no progress bar in the grid + tester.assertChecklistCellInGrid(rowIndex: 0, percent: null); + + // tap on the first checklist cell + await tester.tapChecklistCellInGrid(rowIndex: 0); + + // assert that the checklist editor is shown + tester.assertChecklistEditorVisible(visible: true); + + // assert that new task editor is shown + tester.assertNewCheckListTaskEditorVisible(visible: true); + + // create a new task with enter + await tester.createNewChecklistTask(name: "task 0", enter: true); + + // assert that the task is displayed + tester.assertChecklistTaskInEditor( + index: 0, + name: "task 0", + isChecked: false, + ); + + // update the task's name + await tester.renameChecklistTask(index: 0, name: "task 1"); + + // assert that the task's name is updated + tester.assertChecklistTaskInEditor( + index: 0, + name: "task 1", + isChecked: false, + ); + + // dismiss new task editor + await tester.dismissCellEditor(); + tester.assertNewCheckListTaskEditorVisible(visible: false); + + // dismiss checklist cell editor + await tester.dismissCellEditor(); + + // assert that progress bar is shown in grid at 0% + tester.assertChecklistCellInGrid(rowIndex: 0, percent: 0); + + // start editing the first checklist cell again, click on new task button + await tester.tapChecklistCellInGrid(rowIndex: 0); + tester.assertNewCheckListTaskEditorVisible(visible: false); + await tester.tapChecklistNewTaskButton(); + tester.assertNewCheckListTaskEditorVisible(visible: true); + + // create another task with the create button + await tester.createNewChecklistTask(name: "task 2", button: true); + + // assert that the task was inserted + tester.assertChecklistTaskInEditor( + index: 1, + name: "task 2", + isChecked: false, + ); + + // mark it as complete + await tester.checkChecklistTask(index: 1); + + // assert that the task was checked in the editor + tester.assertChecklistTaskInEditor( + index: 1, + name: "task 2", + isChecked: true, + ); + + // dismiss checklist editor + await tester.dismissCellEditor(); + await tester.dismissCellEditor(); + + // assert that progressbar is shown in grid at 50% + tester.assertChecklistCellInGrid(rowIndex: 0, percent: 0.5); + + // re-open the cell editor + await tester.tapChecklistCellInGrid(rowIndex: 0); + + // hover over first task and delete it + await tester.deleteChecklistTask(index: 0); + + // dismiss cell editor + await tester.dismissCellEditor(); + + // assert that progressbar is shown in grid at 100% + tester.assertChecklistCellInGrid(rowIndex: 0, percent: 1); + + // re-open the cell edior + await tester.tapChecklistCellInGrid(rowIndex: 0); + + // delete the remaining task + await tester.deleteChecklistTask(index: 0); + + // assert that the new task editor is shown + tester.assertNewCheckListTaskEditorVisible(visible: true); + + // dismiss the cell editor + await tester.dismissCellEditor(); + + // check that the progress bar is not viisble + tester.assertChecklistCellInGrid(rowIndex: 0, percent: null); + }); } diff --git a/frontend/appflowy_flutter/integration_test/database_share_test.dart b/frontend/appflowy_flutter/integration_test/database_share_test.dart index d81311028ca3..bd42847f82c1 100644 --- a/frontend/appflowy_flutter/integration_test/database_share_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_share_test.dart @@ -122,17 +122,17 @@ void main() { } // check the checklist cell - final List checklistCells = [ - 0.6, - 0.3, + final List checklistCells = [ + 0.67, + 0.33, 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, + null, + null, + null, + null, + null, + null, + null, ]; for (final (index, percent) in checklistCells.indexed) { await tester.assertChecklistCellInGrid( diff --git a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart index 81ca2e4953b2..685af4184b44 100644 --- a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart'; import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart'; @@ -38,6 +39,7 @@ import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart' import 'package:appflowy/plugins/database_view/widgets/field/grid_property.dart'; import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart'; @@ -243,23 +245,33 @@ extension AppFlowyDatabaseTest on WidgetTester { } } + /// null percent means no progress bar should be found Future assertChecklistCellInGrid({ required int rowIndex, - required double percent, + required double? percent, }) async { final findCell = cellFinder(rowIndex, FieldType.Checklist); - final finder = find.descendant( - of: findCell, - matching: find.byWidgetPredicate( - (widget) { - if (widget is ChecklistProgressBar) { - return widget.percent == percent; - } - return false; - }, - ), - ); - expect(finder, findsOneWidget); + + if (percent == null) { + final finder = find.descendant( + of: findCell, + matching: find.byType(ChecklistProgressBar), + ); + expect(finder, findsNothing); + } else { + final finder = find.descendant( + of: findCell, + matching: find.byWidgetPredicate( + (widget) { + if (widget is ChecklistProgressBar) { + return widget.percent == percent; + } + return false; + }, + ), + ); + expect(finder, findsOneWidget); + } } Future assertDateCellInGrid({ @@ -450,6 +462,119 @@ extension AppFlowyDatabaseTest on WidgetTester { expect(cell, matcher); } + Future tapChecklistCellInGrid({required int rowIndex}) async { + final findRow = find.byType(GridRow); + final findCell = finderForFieldType(FieldType.Checklist); + + final cell = find.descendant( + of: findRow.at(rowIndex), + matching: findCell, + ); + + await tapButton(cell); + } + + void assertChecklistEditorVisible({required bool visible}) { + final editor = find.byType(GridChecklistCellEditor); + if (visible) { + expect(editor, findsOneWidget); + } else { + expect(editor, findsNothing); + } + } + + void assertNewCheckListTaskEditorVisible({required bool visible}) { + final editor = find.byType(NewTaskItem); + if (visible) { + expect(editor, findsOneWidget); + } else { + expect(editor, findsNothing); + } + } + + Future createNewChecklistTask({ + required String name, + enter = false, + button = false, + }) async { + assert(!(enter && button)); + final textField = find.descendant( + of: find.byType(NewTaskItem), + matching: find.byType(TextField), + ); + + await enterText(textField, name); + await pumpAndSettle(const Duration(milliseconds: 300)); + if (enter) { + await testTextInput.receiveAction(TextInputAction.done); + await pumpAndSettle(const Duration(milliseconds: 300)); + } else { + await tapButton( + find.descendant( + of: find.byType(NewTaskItem), + matching: find.byType(FlowyTextButton), + ), + ); + } + } + + void assertChecklistTaskInEditor({ + required int index, + required String name, + required bool isChecked, + }) { + final task = find.byType(ChecklistItem).at(index); + + final widget = this.widget(task); + assert( + widget.option.data.name == name && widget.option.isSelected == isChecked, + ); + } + + Future renameChecklistTask({ + required int index, + required String name, + }) async { + final textField = find + .descendant( + of: find.byType(ChecklistItem), + matching: find.byType(TextField), + ) + .at(index); + + await enterText(textField, name); + await testTextInput.receiveAction(TextInputAction.done); + await pumpAndSettle(const Duration(milliseconds: 300)); + } + + Future tapChecklistNewTaskButton() async { + await tapButton(find.byType(ChecklistNewTaskButton)); + } + + Future checkChecklistTask({required int index}) async { + final button = find.descendant( + of: find.byType(ChecklistItem).at(index), + matching: find.byWidgetPredicate( + (widget) => widget is FlowySvg && widget.svg == FlowySvgs.uncheck_s, + ), + ); + + await tapButton(button); + } + + Future deleteChecklistTask({required int index}) async { + final task = find.byType(ChecklistItem).at(index); + + await startGesture(getCenter(task), kind: PointerDeviceKind.mouse); + await pumpAndSettle(); + + final button = find.byWidgetPredicate( + (widget) => widget is FlowySvg && widget.svg == FlowySvgs.delete_s, + ); + + await tapButton(button); + } + Future openFirstRowDetailPage() async { await hoverOnFirstRowOfGrid(); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/checklist_cell_service.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/checklist_cell_service.dart index 9b38fc1b0d25..dfb9670669b8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/checklist_cell_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/cell/checklist_cell_service.dart @@ -4,6 +4,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb. import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:dartz/dartz.dart'; +import 'package:protobuf/protobuf.dart'; class ChecklistCellBackendService { final String viewId; @@ -52,14 +53,19 @@ class ChecklistCellBackendService { return DatabaseEventUpdateChecklistCell(payload).send(); } - Future> update({ + Future> updateName({ required SelectOptionPB option, + required name, }) { + option.freeze(); + final newOption = option.rebuild((option) { + option.name = name; + }); final payload = ChecklistCellDataChangesetPB.create() ..viewId = viewId ..fieldId = fieldId ..rowId = rowId - ..updateOptions.add(option); + ..updateOptions.add(newOption); return DatabaseEventUpdateChecklistCell(payload).send(); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checklist_card_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checklist_card_cell.dart index 132a59744d18..4d1c538b05f1 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checklist_card_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checklist_card_cell.dart @@ -32,10 +32,16 @@ class _ChecklistCardCellState extends State { return BlocProvider.value( value: _cellBloc, child: BlocBuilder( - builder: (context, state) => Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: ChecklistProgressBar(percent: state.percent), - ), + builder: (context, state) { + if (state.allOptions.isEmpty) { + return const SizedBox.shrink(); + } + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: ChecklistProgressBar(percent: state.percent), + ); + }, ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell.dart index c1b27a5d3da1..f0d9f2280b65 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell.dart @@ -40,7 +40,7 @@ class GridChecklistCellState extends GridCellState { child: AppFlowyPopover( margin: EdgeInsets.zero, controller: _popover, - constraints: BoxConstraints.loose(const Size(260, 400)), + constraints: BoxConstraints.loose(const Size(360, 400)), direction: PopoverDirection.bottomWithLeftAligned, triggerActions: PopoverTriggerFlags.none, popupBuilder: (BuildContext context) { @@ -56,8 +56,12 @@ class GridChecklistCellState extends GridCellState { child: Padding( padding: GridSize.cellContentInsets, child: BlocBuilder( - builder: (context, state) => - ChecklistProgressBar(percent: state.percent), + builder: (context, state) { + if (state.allOptions.isEmpty) { + return const SizedBox.shrink(); + } + return ChecklistProgressBar(percent: state.percent); + }, ), ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_bloc.dart index d8445d7f4be0..7d5b9a865af9 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_bloc.dart @@ -29,13 +29,23 @@ class ChecklistCardCellBloc _loadOptions(); }, didReceiveOptions: (data) { - emit( - state.copyWith( - allOptions: data.options, - selectedOptions: data.selectedOptions, - percent: data.percentage, - ), - ); + if (data == null) { + emit( + const ChecklistCellState( + allOptions: [], + selectedOptions: [], + percent: 0, + ), + ); + } else { + emit( + state.copyWith( + allOptions: data.options, + selectedOptions: data.selectedOptions, + percent: data.percentage, + ), + ); + } }, ); }, @@ -58,7 +68,7 @@ class ChecklistCardCellBloc _loadOptions(); }, onCellChanged: (data) { - if (!isClosed && data != null) { + if (!isClosed) { add(ChecklistCellEvent.didReceiveOptions(data)); } }, @@ -81,7 +91,7 @@ class ChecklistCardCellBloc class ChecklistCellEvent with _$ChecklistCellEvent { const factory ChecklistCellEvent.initial() = _InitialCell; const factory ChecklistCellEvent.didReceiveOptions( - ChecklistCellDataPB data, + ChecklistCellDataPB? data, ) = _DidReceiveCellUpdate; } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart index 65d59fc5efaa..d23a0325f629 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart @@ -1,21 +1,23 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart'; -import 'package:appflowy_popover/appflowy_popover.dart'; - +import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/widgets/common/type_option_separator.dart'; +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../../grid/presentation/layout/sizes.dart'; -import '../../../../grid/presentation/widgets/header/type_option/select_option_editor.dart'; import 'checklist_cell_editor_bloc.dart'; import 'checklist_progress_bar.dart'; class GridChecklistCellEditor extends StatefulWidget { final ChecklistCellController cellController; - const GridChecklistCellEditor({required this.cellController, Key? key}) - : super(key: key); + const GridChecklistCellEditor({required this.cellController, super.key}); @override State createState() => @@ -23,167 +25,367 @@ class GridChecklistCellEditor extends StatefulWidget { } class _GridChecklistCellEditorState extends State { - late ChecklistCellEditorBloc bloc; - late PopoverMutex popoverMutex; + late ChecklistCellEditorBloc _bloc; + + /// Focus node for the new task text field + late final FocusNode newTaskFocusNode; + + /// A flag that determines whether the new task text field is visible + bool _isAddingNewTask = false; @override void initState() { - popoverMutex = PopoverMutex(); - bloc = ChecklistCellEditorBloc(cellController: widget.cellController); - bloc.add(const ChecklistCellEditorEvent.initial()); super.initState(); - } - - @override - void dispose() { - bloc.close(); - super.dispose(); + newTaskFocusNode = FocusNode(); + _bloc = ChecklistCellEditorBloc(cellController: widget.cellController) + ..add(const ChecklistCellEditorEvent.initial()); } @override Widget build(BuildContext context) { return BlocProvider.value( - value: bloc, - child: BlocBuilder( + value: _bloc, + child: BlocConsumer( + listener: (context, state) { + if (state.allOptions.isEmpty) { + setState(() => _isAddingNewTask = true); + } + }, builder: (context, state) { - final List slivers = [ - const SliverChecklistProgressBar(), - SliverToBoxAdapter( - child: ListView.separated( - controller: ScrollController(), - shrinkWrap: true, - itemCount: state.allOptions.length, - itemBuilder: (BuildContext context, int index) { - return _ChecklistOptionCell( - option: state.allOptions[index], - popoverMutex: popoverMutex, - ); - }, - separatorBuilder: (BuildContext context, int index) { - return VSpace(GridSize.typeOptionSeparatorHeight); - }, - ), - ), - ]; - - return Padding( - padding: const EdgeInsets.all(8.0), - child: ScrollConfiguration( - behavior: const ScrollBehavior().copyWith(scrollbars: false), - child: CustomScrollView( - shrinkWrap: true, - slivers: slivers, - controller: ScrollController(), - physics: StyledScrollPhysics(), - ), + return Focus( + onKey: (node, event) { + // don't hide new task text field if there are no tasks at all + if (state.allOptions.isNotEmpty && + event is RawKeyDownEvent && + event.logicalKey == LogicalKeyboardKey.escape) { + setState(() { + _isAddingNewTask = false; + }); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: CustomScrollView( + shrinkWrap: true, + physics: StyledScrollPhysics(), + slivers: [ + SliverToBoxAdapter( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: state.allOptions.isEmpty + ? const SizedBox.shrink() + : Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 4), + child: ChecklistProgressBar( + percent: state.percent, + ), + ), + ), + ), + ChecklistItemList( + options: state.allOptions, + newTaskFocusNode: newTaskFocusNode, + isAddingNewTask: _isAddingNewTask, + onUpdateTask: () => setState(() { + _isAddingNewTask = true; + newTaskFocusNode.requestFocus(); + }), + ), + const SliverToBoxAdapter( + child: TypeOptionSeparator(spacing: 0.0), + ), + SliverToBoxAdapter( + child: ChecklistNewTaskButton( + onTap: () => setState(() => _isAddingNewTask = true), + ), + ), + ], ), ); }, ), ); } + + @override + void dispose() { + _bloc.close(); + super.dispose(); + } +} + +/// Displays the a list of all the exisiting tasks and an input field to create +/// a new task if `isAddingNewTask` is true +class ChecklistItemList extends StatefulWidget { + final List options; + final FocusNode newTaskFocusNode; + final bool isAddingNewTask; + final VoidCallback onUpdateTask; + + const ChecklistItemList({ + super.key, + required this.options, + required this.onUpdateTask, + required this.isAddingNewTask, + required this.newTaskFocusNode, + }); + + @override + State createState() => _ChecklistItemListState(); +} + +class _ChecklistItemListState extends State { + @override + Widget build(BuildContext context) { + final itemList = [ + const VSpace(6.0), + ...widget.options.mapIndexed( + (index, option) => Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: ChecklistItem( + option: option, + onSubmitted: + index == widget.options.length - 1 ? widget.onUpdateTask : null, + key: ValueKey(option.data.id), + // only allow calling the callback for the last task in the list + ), + ), + ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: widget.isAddingNewTask + ? NewTaskItem(focusNode: widget.newTaskFocusNode) + : const SizedBox.shrink(), + ), + const VSpace(6.0), + ]; + return SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) => itemList[index], + childCount: itemList.length, + ), + ); + } } -class _ChecklistOptionCell extends StatefulWidget { +/// Represents an existing task +@visibleForTesting +class ChecklistItem extends StatefulWidget { final ChecklistSelectOption option; - final PopoverMutex popoverMutex; - const _ChecklistOptionCell({ + final VoidCallback? onSubmitted; + const ChecklistItem({ required this.option, - required this.popoverMutex, Key? key, + this.onSubmitted, }) : super(key: key); @override - State<_ChecklistOptionCell> createState() => _ChecklistOptionCellState(); + State createState() => _ChecklistItemState(); } -class _ChecklistOptionCellState extends State<_ChecklistOptionCell> { - late PopoverController _popoverController; +class _ChecklistItemState extends State { + late final TextEditingController _textController; + late final FocusNode _focusNode; + bool _isHovered = false; @override void initState() { - _popoverController = PopoverController(); super.initState(); + _textController = TextEditingController(text: widget.option.data.name); + _focusNode = FocusNode( + onKey: (node, event) { + if (event is RawKeyDownEvent && + event.logicalKey == LogicalKeyboardKey.escape) { + node.unfocus(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + ); } @override Widget build(BuildContext context) { - final icon = widget.option.isSelected - ? const FlowySvg( - FlowySvgs.check_filled_s, - blendMode: BlendMode.dst, - ) - : const FlowySvg(FlowySvgs.uncheck_s); - return _wrapPopover( - SizedBox( - height: GridSize.popoverItemHeight, - child: Row( - children: [ - Expanded( - child: FlowyButton( - hoverColor: AFThemeExtension.of(context).lightGreyHover, - text: FlowyText( - widget.option.data.name, - color: AFThemeExtension.of(context).textColor, + final icon = FlowySvg( + widget.option.isSelected ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s, + blendMode: BlendMode.dst, + ); + return MouseRegion( + onEnter: (event) => setState(() => _isHovered = true), + onExit: (event) => setState(() => _isHovered = false), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + constraints: BoxConstraints(minHeight: GridSize.popoverItemHeight), + child: DecoratedBox( + decoration: BoxDecoration( + color: _isHovered + ? AFThemeExtension.of(context).lightGreyHover + : Colors.transparent, + borderRadius: Corners.s6Border, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + FlowyIconButton( + width: 32, + icon: icon, + hoverColor: Colors.transparent, + onPressed: () => context.read().add( + ChecklistCellEditorEvent.selectTask(widget.option.data), + ), + ), + Expanded( + child: TextField( + controller: _textController, + focusNode: _focusNode, + style: Theme.of(context).textTheme.bodyMedium, + maxLines: 1, + decoration: InputDecoration( + border: InputBorder.none, + isCollapsed: true, + contentPadding: const EdgeInsets.symmetric( + vertical: 6.0, + horizontal: 2.0, + ), + hintText: LocaleKeys.grid_checklist_taskHint.tr(), + ), + onSubmitted: (taskDescription) { + context.read().add( + ChecklistCellEditorEvent.updateTaskName( + widget.option.data, + taskDescription, + ), + ); + widget.onSubmitted?.call(); + }, ), - leftIcon: icon, - onTap: () => context - .read() - .add(ChecklistCellEditorEvent.selectOption(widget.option)), ), - ), - _disclosureButton(), - ], + if (_isHovered) + FlowyIconButton( + width: 32, + icon: const FlowySvg(FlowySvgs.delete_s), + hoverColor: Colors.transparent, + iconColorOnHover: Theme.of(context).colorScheme.error, + onPressed: () => context.read().add( + ChecklistCellEditorEvent.deleteTask(widget.option.data), + ), + ), + ], + ), ), ), ); } +} + +/// Creates a new task after entering the description and pressing enter. +/// This can be cancelled by pressing escape +@visibleForTesting +class NewTaskItem extends StatefulWidget { + final FocusNode focusNode; + const NewTaskItem({super.key, required this.focusNode}); + + @override + State createState() => _NewTaskItemState(); +} + +class _NewTaskItemState extends State { + late final TextEditingController _textEditingController; + + @override + void initState() { + super.initState(); + _textEditingController = TextEditingController(); + if (widget.focusNode.canRequestFocus) { + widget.focusNode.requestFocus(); + } + } - Widget _disclosureButton() { - return FlowyIconButton( - hoverColor: AFThemeExtension.of(context).lightGreyHover, - width: 20, - onPressed: () => _popoverController.show(), - iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2), - icon: FlowySvg( - FlowySvgs.details_s, - color: Theme.of(context).iconTheme.color, + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + constraints: BoxConstraints(minHeight: GridSize.popoverItemHeight), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const FlowyIconButton( + width: 32, + icon: FlowySvg( + FlowySvgs.uncheck_s, + blendMode: BlendMode.dst, + ), + hoverColor: Colors.transparent, + ), + Expanded( + child: TextField( + focusNode: widget.focusNode, + controller: _textEditingController, + style: Theme.of(context).textTheme.bodyMedium, + maxLines: 1, + decoration: InputDecoration( + border: InputBorder.none, + isCollapsed: true, + contentPadding: const EdgeInsets.symmetric( + vertical: 6.0, + horizontal: 2.0, + ), + hintText: LocaleKeys.grid_checklist_taskHint.tr(), + ), + onSubmitted: (taskDescription) { + if (taskDescription.trim().isNotEmpty) { + context.read().add( + ChecklistCellEditorEvent.newTask( + taskDescription.trim(), + ), + ); + } + _textEditingController.clear(); + }, + ), + ), + FlowyTextButton( + LocaleKeys.grid_checklist_submitNewTask.tr(), + fontSize: 11, + fillColor: Theme.of(context).colorScheme.primary, + hoverColor: Theme.of(context).colorScheme.primaryContainer, + fontColor: Theme.of(context).colorScheme.onPrimary, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + onPressed: () { + if (_textEditingController.text.trim().isNotEmpty) { + context.read().add( + ChecklistCellEditorEvent.newTask( + _textEditingController.text..trim(), + ), + ); + } + _textEditingController.clear(); + }, + ), + ], ), ); } +} - Widget _wrapPopover(Widget child) { - return AppFlowyPopover( - controller: _popoverController, - offset: const Offset(8, 0), - asBarrier: true, - constraints: BoxConstraints.loose(const Size(200, 300)), - mutex: widget.popoverMutex, - triggerActions: PopoverTriggerFlags.none, - child: child, - popupBuilder: (BuildContext popoverContext) { - return SelectOptionTypeOptionEditor( - option: widget.option.data, - onDeleted: () { - context.read().add( - ChecklistCellEditorEvent.deleteOption(widget.option.data), - ); - - _popoverController.close(); - }, - onUpdated: (updatedOption) { - context.read().add( - ChecklistCellEditorEvent.updateOption(updatedOption), - ); - }, - showOptions: false, - autoFocus: false, - // Use ValueKey to refresh the UI, otherwise, it will remain the old value. - key: ValueKey( - widget.option.data.id, - ), - ); - }, +@visibleForTesting +class ChecklistNewTaskButton extends StatelessWidget { + final VoidCallback onTap; + const ChecklistNewTaskButton({super.key, required this.onTap}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), + child: SizedBox( + height: 30, + child: FlowyButton( + text: FlowyText.medium(LocaleKeys.grid_checklist_addNew.tr()), + margin: const EdgeInsets.all(6), + leftIcon: const FlowySvg(FlowySvgs.add_s), + onTap: onTap, + ), + ), ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart index fbcd994e7fe0..55185a521ceb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart @@ -11,6 +11,13 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'checklist_cell_editor_bloc.freezed.dart'; +class ChecklistSelectOption { + final bool isSelected; + final SelectOptionPB data; + + ChecklistSelectOption(this.isSelected, this.data); +} + class ChecklistCellEditorBloc extends Bloc { final ChecklistCellBackendService _checklistCellService; @@ -31,33 +38,31 @@ class ChecklistCellEditorBloc _startListening(); _loadOptions(); }, - didReceiveOptions: (data) { + didReceiveTasks: (data) { emit( state.copyWith( - allOptions: _makeChecklistSelectOptions(data, state.predicate), - percent: data.percentage, + allOptions: _makeChecklistSelectOptions(data), + percent: data?.percentage ?? 0, ), ); }, - newOption: (optionName) { - _createOption(optionName); + newTask: (optionName) async { + await _createOption(optionName); emit( state.copyWith( createOption: Some(optionName), - predicate: '', ), ); }, - deleteOption: (option) { - _deleteOption([option]); + deleteTask: (option) async { + await _deleteOption([option]); }, - updateOption: (option) { - _updateOption(option); + updateTaskName: (option, name) { + _updateOption(option, name); }, - selectOption: (option) async { - await _checklistCellService.select(optionId: option.data.id); + selectTask: (option) async { + await _checklistCellService.select(optionId: option.id); }, - filterOption: (String predicate) {}, ); }, ); @@ -69,22 +74,21 @@ class ChecklistCellEditorBloc return super.close(); } - void _createOption(String name) async { + Future _createOption(String name) async { final result = await _checklistCellService.create(name: name); result.fold((l) => {}, (err) => Log.error(err)); } - void _deleteOption(List options) async { + Future _deleteOption(List options) async { final result = await _checklistCellService.delete( optionIds: options.map((e) => e.id).toList(), ); result.fold((l) => null, (err) => Log.error(err)); } - void _updateOption(SelectOptionPB option) async { - final result = await _checklistCellService.update( - option: option, - ); + void _updateOption(SelectOptionPB option, String name) async { + final result = + await _checklistCellService.updateName(option: option, name: name); result.fold((l) => null, (err) => Log.error(err)); } @@ -94,7 +98,7 @@ class ChecklistCellEditorBloc if (isClosed) return; return result.fold( - (data) => add(ChecklistCellEditorEvent.didReceiveOptions(data)), + (data) => add(ChecklistCellEditorEvent.didReceiveTasks(data)), (err) => Log.error(err), ); }); @@ -103,8 +107,8 @@ class ChecklistCellEditorBloc void _startListening() { cellController.startListening( onCellChanged: ((data) { - if (!isClosed && data != null) { - add(ChecklistCellEditorEvent.didReceiveOptions(data)); + if (!isClosed) { + add(ChecklistCellEditorEvent.didReceiveTasks(data)); } }), onCellFieldChanged: () { @@ -117,20 +121,19 @@ class ChecklistCellEditorBloc @freezed class ChecklistCellEditorEvent with _$ChecklistCellEditorEvent { const factory ChecklistCellEditorEvent.initial() = _Initial; - const factory ChecklistCellEditorEvent.didReceiveOptions( - ChecklistCellDataPB data, - ) = _DidReceiveOptions; - const factory ChecklistCellEditorEvent.newOption(String optionName) = - _NewOption; - const factory ChecklistCellEditorEvent.selectOption( - ChecklistSelectOption option, - ) = _SelectOption; - const factory ChecklistCellEditorEvent.updateOption(SelectOptionPB option) = - _UpdateOption; - const factory ChecklistCellEditorEvent.deleteOption(SelectOptionPB option) = - _DeleteOption; - const factory ChecklistCellEditorEvent.filterOption(String predicate) = - _FilterOption; + const factory ChecklistCellEditorEvent.didReceiveTasks( + ChecklistCellDataPB? data, + ) = _DidReceiveTasks; + const factory ChecklistCellEditorEvent.newTask(String taskName) = _NewOption; + const factory ChecklistCellEditorEvent.selectTask( + SelectOptionPB option, + ) = _SelectTask; + const factory ChecklistCellEditorEvent.updateTaskName( + SelectOptionPB option, + String name, + ) = _UpdateTaskName; + const factory ChecklistCellEditorEvent.deleteTask(SelectOptionPB option) = + _DeleteTask; } @freezed @@ -139,24 +142,21 @@ class ChecklistCellEditorState with _$ChecklistCellEditorState { required List allOptions, required Option createOption, required double percent, - required String predicate, }) = _ChecklistCellEditorState; factory ChecklistCellEditorState.initial(ChecklistCellController context) { final data = context.getCellData(loadIfNotExist: true); return ChecklistCellEditorState( - allOptions: _makeChecklistSelectOptions(data, ''), + allOptions: _makeChecklistSelectOptions(data), createOption: none(), percent: data?.percentage ?? 0, - predicate: '', ); } } List _makeChecklistSelectOptions( ChecklistCellDataPB? data, - String predicate, ) { if (data == null) { return []; @@ -164,9 +164,6 @@ List _makeChecklistSelectOptions( final List options = []; final List allOptions = List.from(data.options); - if (predicate.isNotEmpty) { - allOptions.retainWhere((element) => element.name.contains(predicate)); - } final selectedOptionIds = data.selectedOptions.map((e) => e.id).toList(); for (final option in allOptions) { @@ -177,10 +174,3 @@ List _makeChecklistSelectOptions( return options; } - -class ChecklistSelectOption { - final bool isSelected; - final SelectOptionPB data; - - ChecklistSelectOption(this.isSelected, this.data); -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart index 04fe36dcd0fd..289d84b25be2 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart @@ -1,129 +1,44 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; -import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart'; -import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:percent_indicator/percent_indicator.dart'; -class ChecklistProgressBar extends StatelessWidget { +class ChecklistProgressBar extends StatefulWidget { final double percent; const ChecklistProgressBar({required this.percent, Key? key}) : super(key: key); @override - Widget build(BuildContext context) { - return LinearPercentIndicator( - lineHeight: 10.0, - percent: percent, - padding: EdgeInsets.zero, - progressColor: percent < 1.0 - ? SelectOptionColorPB.Purple.toColor(context) - : SelectOptionColorPB.Green.toColor(context), - backgroundColor: AFThemeExtension.of(context).progressBarBGColor, - barRadius: const Radius.circular(5), - ); - } + State createState() => _ChecklistProgressBarState(); } -class SliverChecklistProgressBar extends StatelessWidget { - const SliverChecklistProgressBar({Key? key}) : super(key: key); - +class _ChecklistProgressBarState extends State { @override Widget build(BuildContext context) { - return SliverPersistentHeader( - pinned: true, - delegate: _SliverChecklistProgressBarDelegate(), - ); - } -} - -class _SliverChecklistProgressBarDelegate - extends SliverPersistentHeaderDelegate { - _SliverChecklistProgressBarDelegate(); - - double fixHeight = 60; - - @override - Widget build( - BuildContext context, - double shrinkOffset, - bool overlapsContent, - ) { - return const _AutoFocusTextField(); - } - - @override - double get maxExtent => fixHeight; - - @override - double get minExtent => fixHeight; - - @override - bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { - return true; - } -} - -class _AutoFocusTextField extends StatefulWidget { - const _AutoFocusTextField(); - - @override - State<_AutoFocusTextField> createState() => _AutoFocusTextFieldState(); -} - -class _AutoFocusTextFieldState extends State<_AutoFocusTextField> { - final _focusNode = FocusNode(); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return BlocListener( - listenWhen: (previous, current) => - previous.createOption != current.createOption, - listener: (context, state) { - if (_focusNode.canRequestFocus) { - _focusNode.requestFocus(); - } - }, - child: Container( - color: Theme.of(context).cardColor, - child: Padding( - padding: GridSize.typeOptionContentInsets, - child: Column( - children: [ - FlowyTextField( - autoFocus: true, - focusNode: _focusNode, - autoClearWhenDone: true, - submitOnLeave: true, - hintText: LocaleKeys.grid_checklist_panelTitle.tr(), - onChanged: (text) { - context - .read() - .add(ChecklistCellEditorEvent.filterOption(text)); - }, - onSubmitted: (text) { - context - .read() - .add(ChecklistCellEditorEvent.newOption(text)); - }, - ), - Padding( - padding: const EdgeInsets.only(top: 6.0), - child: ChecklistProgressBar(percent: state.percent), - ), - ], - ), + return Row( + children: [ + Expanded( + child: LinearPercentIndicator( + lineHeight: 4.0, + percent: widget.percent, + padding: EdgeInsets.zero, + progressColor: Theme.of(context).colorScheme.primary, + backgroundColor: AFThemeExtension.of(context).progressBarBGColor, + barRadius: const Radius.circular(5), + ), + ), + SizedBox( + width: 36, + child: Align( + alignment: AlignmentDirectional.centerEnd, + child: FlowyText.regular( + "${(widget.percent * 100).round()}%", + fontSize: 11, + color: Theme.of(context).hintColor, ), ), - ); - }, + ), + ], ); } } diff --git a/frontend/appflowy_flutter/packages/appflowy_popover/lib/src/mask.dart b/frontend/appflowy_flutter/packages/appflowy_popover/lib/src/mask.dart index d63e486a1e7b..30c744902e99 100644 --- a/frontend/appflowy_flutter/packages/appflowy_popover/lib/src/mask.dart +++ b/frontend/appflowy_flutter/packages/appflowy_popover/lib/src/mask.dart @@ -2,7 +2,6 @@ import 'dart:collection'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; typedef EntryMap = LinkedHashMap; @@ -67,50 +66,19 @@ class OverlayEntryContext { ); } -class PopoverMask extends StatefulWidget { +class PopoverMask extends StatelessWidget { final void Function() onTap; - final void Function()? onExit; final Decoration? decoration; - const PopoverMask( - {Key? key, required this.onTap, this.onExit, this.decoration}) + const PopoverMask({Key? key, required this.onTap, this.decoration}) : super(key: key); - @override - State createState() => _PopoverMaskState(); -} - -class _PopoverMaskState extends State { - @override - void initState() { - HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent); - super.initState(); - } - - bool _handleGlobalKeyEvent(KeyEvent event) { - if (event.logicalKey == LogicalKeyboardKey.escape && - event is KeyDownEvent) { - if (widget.onExit != null) { - widget.onExit!(); - } - return true; - } else { - return false; - } - } - - @override - void deactivate() { - HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent); - super.deactivate(); - } - @override Widget build(BuildContext context) { return GestureDetector( - onTap: widget.onTap, + onTap: onTap, child: Container( - decoration: widget.decoration, + decoration: decoration, ), ); } diff --git a/frontend/appflowy_flutter/packages/appflowy_popover/lib/src/popover.dart b/frontend/appflowy_flutter/packages/appflowy_popover/lib/src/popover.dart index fdb3628011b9..105fc7729364 100644 --- a/frontend/appflowy_flutter/packages/appflowy_popover/lib/src/popover.dart +++ b/frontend/appflowy_flutter/packages/appflowy_popover/lib/src/popover.dart @@ -1,5 +1,6 @@ import 'package:appflowy_popover/src/layout.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'mask.dart'; import 'mutex.dart'; @@ -130,7 +131,6 @@ class PopoverState extends State { } _removeRootOverlay(); }, - onExit: () => _removeRootOverlay(), ), ); } @@ -147,7 +147,17 @@ class PopoverState extends State { ), ); - return Stack(children: children); + return FocusScope( + onKey: (node, event) { + if (event is RawKeyDownEvent && + event.logicalKey == LogicalKeyboardKey.escape) { + _removeRootOverlay(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: Stack(children: children), + ); }); _rootEntry.addEntry(context, this, newEntry, widget.asBarrier); } @@ -192,9 +202,9 @@ class PopoverState extends State { showOverlay(); } }, - child: Listener( + child: GestureDetector( child: widget.child, - onPointerDown: (PointerDownEvent event) { + onTap: () { if (widget.triggerActions & PopoverTriggerFlags.click != 0) { showOverlay(); } @@ -240,14 +250,17 @@ class PopoverContainer extends StatefulWidget { class PopoverContainerState extends State { @override Widget build(BuildContext context) { - return CustomSingleChildLayout( - delegate: PopoverLayoutDelegate( - direction: widget.direction, - link: widget.popoverLink, - offset: widget.offset, - windowPadding: widget.windowPadding, + return Focus( + autofocus: true, + child: CustomSingleChildLayout( + delegate: PopoverLayoutDelegate( + direction: widget.direction, + link: widget.popoverLink, + offset: widget.offset, + windowPadding: widget.windowPadding, + ), + child: widget.popupBuilder(context), ), - child: widget.popupBuilder(context), ); } diff --git a/frontend/resources/translations/ar-SA.json b/frontend/resources/translations/ar-SA.json index d01c54d637a7..bdc8bbed840b 100644 --- a/frontend/resources/translations/ar-SA.json +++ b/frontend/resources/translations/ar-SA.json @@ -386,7 +386,7 @@ "searchOption": "ابحث عن خيار" }, "checklist": { - "panelTitle": "أضف عنصرًا" + "addNew": "أضف عنصرًا" }, "menuName": "شبكة", "referencedGridPrefix": "نظرا ل", diff --git a/frontend/resources/translations/ca-ES.json b/frontend/resources/translations/ca-ES.json index 04dd592c9dd6..295fe727c691 100644 --- a/frontend/resources/translations/ca-ES.json +++ b/frontend/resources/translations/ca-ES.json @@ -402,7 +402,7 @@ "searchOption": "Cerca una opció" }, "checklist": { - "panelTitle": "Afegeix un element" + "addNew": "Afegeix un element" }, "menuName": "Quadrícula", "referencedGridPrefix": "Vista de" diff --git a/frontend/resources/translations/de-DE.json b/frontend/resources/translations/de-DE.json index 3a748c22d3c6..011ce8b13dcb 100644 --- a/frontend/resources/translations/de-DE.json +++ b/frontend/resources/translations/de-DE.json @@ -411,7 +411,7 @@ "searchOption": "Suchen Sie nach einer Option" }, "checklist": { - "panelTitle": "Fügen Sie einen Artikel hinzu" + "addNew": "Fügen Sie einen Artikel hinzu" }, "menuName": "Netz", "referencedGridPrefix": "Sicht von" diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 480c8bb3b767..a74803a7d80f 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -477,7 +477,9 @@ "orSelectOne": "Or select an option" }, "checklist": { - "panelTitle": "Add an item" + "taskHint": "Task description", + "addNew": "Add a new task", + "submitNewTask": "Create" }, "menuName": "Grid", "referencedGridPrefix": "View of" diff --git a/frontend/resources/translations/es-VE.json b/frontend/resources/translations/es-VE.json index b11783a7ce1d..68b81f2c808f 100644 --- a/frontend/resources/translations/es-VE.json +++ b/frontend/resources/translations/es-VE.json @@ -377,7 +377,7 @@ "addSort": "Agregar clasificación" }, "checklist": { - "panelTitle": "Agregar un elemento" + "addNew": "Agregar un elemento" }, "referencedGridPrefix": "Vista de" }, diff --git a/frontend/resources/translations/eu-ES.json b/frontend/resources/translations/eu-ES.json index 7506bde115d7..fc7e809e080c 100644 --- a/frontend/resources/translations/eu-ES.json +++ b/frontend/resources/translations/eu-ES.json @@ -387,7 +387,7 @@ "searchOption": "Aukera bat bilatu" }, "checklist": { - "panelTitle": "Gehitu elementu bat" + "addNew": "Gehitu elementu bat" }, "menuName": "Sareta", "deleteView": "Ziur ikuspegi hau ezabatu nahi duzula?", diff --git a/frontend/resources/translations/fa.json b/frontend/resources/translations/fa.json index 4836986395f8..424ffc5846f7 100644 --- a/frontend/resources/translations/fa.json +++ b/frontend/resources/translations/fa.json @@ -439,7 +439,7 @@ "searchOption": "جستجوی یک گزینه" }, "checklist": { - "panelTitle": "یک مورد اضافه کنید" + "addNew": "یک مورد اضافه کنید" }, "menuName": "شبکه‌ای", "referencedGridPrefix": "نمایش" diff --git a/frontend/resources/translations/fr-CA.json b/frontend/resources/translations/fr-CA.json index b817c1c6d4ca..ace3de14a45f 100644 --- a/frontend/resources/translations/fr-CA.json +++ b/frontend/resources/translations/fr-CA.json @@ -402,7 +402,7 @@ "searchOption": "Rechercher une option" }, "checklist": { - "panelTitle": "Ajouter un article" + "addNew": "Ajouter un article" }, "menuName": "Grille", "referencedGridPrefix": "Vue" diff --git a/frontend/resources/translations/fr-FR.json b/frontend/resources/translations/fr-FR.json index 65469a59617d..12f507cd898e 100644 --- a/frontend/resources/translations/fr-FR.json +++ b/frontend/resources/translations/fr-FR.json @@ -381,7 +381,7 @@ "addSort": "Ajouter un tri" }, "checklist": { - "panelTitle": "Ajouter un élément" + "addNew": "Ajouter un élément" }, "referencedGridPrefix": "Vue" }, diff --git a/frontend/resources/translations/hu-HU.json b/frontend/resources/translations/hu-HU.json index e7ce79497f78..1d7a2f58766e 100644 --- a/frontend/resources/translations/hu-HU.json +++ b/frontend/resources/translations/hu-HU.json @@ -402,7 +402,7 @@ "searchOption": "Keressen egy lehetőséget" }, "checklist": { - "panelTitle": "Adjon hozzá egy elemet" + "addNew": "Adjon hozzá egy elemet" }, "menuName": "Rács", "referencedGridPrefix": "Nézet" diff --git a/frontend/resources/translations/id-ID.json b/frontend/resources/translations/id-ID.json index b49ce40b2172..700c85da6b9e 100644 --- a/frontend/resources/translations/id-ID.json +++ b/frontend/resources/translations/id-ID.json @@ -377,7 +377,7 @@ "addSort": "Tambahkan semacam" }, "checklist": { - "panelTitle": "Tambahkan item" + "addNew": "Tambahkan item" }, "referencedGridPrefix": "Pemandangan dari" }, diff --git a/frontend/resources/translations/it-IT.json b/frontend/resources/translations/it-IT.json index bbba1c702aa1..507c07d5cdf3 100644 --- a/frontend/resources/translations/it-IT.json +++ b/frontend/resources/translations/it-IT.json @@ -370,7 +370,7 @@ "searchOption": "Cerca un'opzione" }, "checklist": { - "panelTitle": "Aggiungi un elemento" + "addNew": "Aggiungi un elemento" }, "referencedGridPrefix": "Vista di" }, diff --git a/frontend/resources/translations/ja-JP.json b/frontend/resources/translations/ja-JP.json index 6879d400accb..7d92b02cb474 100644 --- a/frontend/resources/translations/ja-JP.json +++ b/frontend/resources/translations/ja-JP.json @@ -370,7 +370,7 @@ "addSort": "並べ替えの追加" }, "checklist": { - "panelTitle": "アイテムを追加する" + "addNew": "アイテムを追加する" }, "menuName": "グリッド", "referencedGridPrefix": "のビュー" diff --git a/frontend/resources/translations/ko-KR.json b/frontend/resources/translations/ko-KR.json index 91e41afba3e0..44eee7a7b801 100644 --- a/frontend/resources/translations/ko-KR.json +++ b/frontend/resources/translations/ko-KR.json @@ -383,7 +383,7 @@ "addSort": "정렬 추가" }, "checklist": { - "panelTitle": "항목 추가" + "addNew": "항목 추가" }, "referencedGridPrefix": "관점" }, diff --git a/frontend/resources/translations/pl-PL.json b/frontend/resources/translations/pl-PL.json index a97f6d2b7439..8b6cbaf18a17 100644 --- a/frontend/resources/translations/pl-PL.json +++ b/frontend/resources/translations/pl-PL.json @@ -402,7 +402,7 @@ "searchOption": "Wyszukaj opcję" }, "checklist": { - "panelTitle": "Dodaj element" + "addNew": "Dodaj element" }, "menuName": "Siatka", "referencedGridPrefix": "Widok" diff --git a/frontend/resources/translations/pt-BR.json b/frontend/resources/translations/pt-BR.json index fb6e11dc8564..c3a38e37f17a 100644 --- a/frontend/resources/translations/pt-BR.json +++ b/frontend/resources/translations/pt-BR.json @@ -438,7 +438,7 @@ "panelTitle": "Selecione uma opção ou crie uma" }, "checklist": { - "panelTitle": "Adicionar um item" + "addNew": "Adicionar um item" }, "menuName": "Grade", "deleteView": "Tem certeza de que deseja excluir esta visualização?", diff --git a/frontend/resources/translations/pt-PT.json b/frontend/resources/translations/pt-PT.json index e8f55527590e..26f954fbba88 100644 --- a/frontend/resources/translations/pt-PT.json +++ b/frontend/resources/translations/pt-PT.json @@ -455,7 +455,7 @@ "searchOption": "Pesquise uma opção" }, "checklist": { - "panelTitle": "Adicionar um item" + "addNew": "Adicionar um item" }, "menuName": "Grade", "referencedGridPrefix": "Vista de" diff --git a/frontend/resources/translations/ru-RU.json b/frontend/resources/translations/ru-RU.json index 9345fc3e783d..af67e821094c 100644 --- a/frontend/resources/translations/ru-RU.json +++ b/frontend/resources/translations/ru-RU.json @@ -393,7 +393,7 @@ "searchOption": "Поиск" }, "checklist": { - "panelTitle": "Добавить элемент" + "addNew": "Добавить элемент" }, "menuName": "Сетка", "referencedGridPrefix": "Просмотр", diff --git a/frontend/resources/translations/sv.json b/frontend/resources/translations/sv.json index 45e3efceec89..9844ae89ece8 100644 --- a/frontend/resources/translations/sv.json +++ b/frontend/resources/translations/sv.json @@ -381,7 +381,7 @@ "addSort": "Lägg till sortering" }, "checklist": { - "panelTitle": "Lägg till ett objekt" + "addNew": "Lägg till ett objekt" }, "referencedGridPrefix": "Utsikt över" }, diff --git a/frontend/resources/translations/tr-TR.json b/frontend/resources/translations/tr-TR.json index 58f7a60aee95..fe7ba3744075 100644 --- a/frontend/resources/translations/tr-TR.json +++ b/frontend/resources/translations/tr-TR.json @@ -402,7 +402,7 @@ "searchOption": "Bir seçenek arayın" }, "checklist": { - "panelTitle": "öğe ekle" + "addNew": "öğe ekle" }, "menuName": "Kafes", "referencedGridPrefix": "görünümü" diff --git a/frontend/resources/translations/zh-CN.json b/frontend/resources/translations/zh-CN.json index d7e64bddc298..0b374fb2df2f 100644 --- a/frontend/resources/translations/zh-CN.json +++ b/frontend/resources/translations/zh-CN.json @@ -397,7 +397,7 @@ "searchOption": "搜索标签" }, "checklist": { - "panelTitle": "添加项" + "addNew": "添加项" }, "menuName": "网格", "referencedGridPrefix": "视图" diff --git a/frontend/resources/translations/zh-TW.json b/frontend/resources/translations/zh-TW.json index 4912a13c506b..b637da3a4c8a 100644 --- a/frontend/resources/translations/zh-TW.json +++ b/frontend/resources/translations/zh-TW.json @@ -386,7 +386,7 @@ "searchOption": "搜尋選項" }, "checklist": { - "panelTitle": "新增物件" + "addNew": "新增物件" }, "menuName": "網格", "deleteView": "您確定要刪除該視圖嗎?", diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs index 95b22a0c75fe..d0ff75ac3380 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs @@ -42,7 +42,7 @@ impl ChecklistCellData { if total_options == 0 { return 0.0; } - ((selected_options as f64) / (total_options as f64) * 10.0).trunc() / 10.0 + ((selected_options as f64) / (total_options as f64) * 100.0).round() / 100.0 } pub fn from_options(options: Vec) -> Self { diff --git a/frontend/rust-lib/flowy-test/tests/database/local_test/test.rs b/frontend/rust-lib/flowy-test/tests/database/local_test/test.rs index 8d953198b513..e24c00747255 100644 --- a/frontend/rust-lib/flowy-test/tests/database/local_test/test.rs +++ b/frontend/rust-lib/flowy-test/tests/database/local_test/test.rs @@ -653,7 +653,7 @@ async fn update_checklist_cell_test() { assert_eq!(cell.options.len(), 3); assert_eq!(cell.selected_options.len(), 2); - assert_eq!(cell.percentage, 0.6); + assert_eq!(cell.percentage, 0.67); } // The number of groups should be 0 if there is no group by field in grid From 9231455f0dceb5423b8bbc2aad3e01565119107e Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Wed, 13 Sep 2023 22:11:51 +0800 Subject: [PATCH 08/14] chore: update collab rev and fix compile (#3398) --- frontend/appflowy_tauri/src-tauri/Cargo.lock | 34 ++++++++++++------- frontend/appflowy_tauri/src-tauri/Cargo.toml | 18 +++++----- frontend/rust-lib/Cargo.lock | 34 ++++++++++++------- frontend/rust-lib/Cargo.toml | 16 ++++----- frontend/rust-lib/flowy-core/Cargo.toml | 1 + .../flowy-core/src/integrate/server.rs | 3 +- .../rust-lib/flowy-database-deps/Cargo.toml | 2 +- .../rust-lib/flowy-database-deps/src/cloud.rs | 2 +- frontend/rust-lib/flowy-database2/Cargo.toml | 1 + .../rust-lib/flowy-database2/src/manager.rs | 3 +- frontend/rust-lib/flowy-document2/Cargo.toml | 1 + .../rust-lib/flowy-document2/src/manager.rs | 3 +- frontend/rust-lib/flowy-folder2/Cargo.toml | 1 + .../rust-lib/flowy-folder2/src/manager.rs | 3 +- frontend/rust-lib/flowy-server/Cargo.toml | 1 + .../src/af_cloud/impls/database.rs | 2 +- .../flowy-server/src/af_cloud/impls/user.rs | 2 +- .../flowy-server/src/af_cloud/server.rs | 3 +- .../src/local_server/impls/database.rs | 2 +- .../src/local_server/impls/user.rs | 2 +- .../flowy-server/src/local_server/server.rs | 3 +- frontend/rust-lib/flowy-server/src/server.rs | 3 +- .../src/supabase/api/collab_storage.rs | 4 +-- .../flowy-server/src/supabase/api/database.rs | 2 +- .../flowy-server/src/supabase/api/document.rs | 2 +- .../flowy-server/src/supabase/api/folder.rs | 2 +- .../flowy-server/src/supabase/api/request.rs | 3 +- .../flowy-server/src/supabase/api/user.rs | 2 +- .../flowy-server/src/supabase/define.rs | 2 +- .../flowy-server/src/supabase/server.rs | 3 +- .../tests/supabase_test/database_test.rs | 2 +- .../tests/supabase_test/folder_test.rs | 2 +- frontend/rust-lib/flowy-user/Cargo.toml | 1 + .../flowy-user/src/entities/reminder.rs | 3 +- .../src/migrations/sync_new_user.rs | 3 +- .../flowy-user/src/services/user_awareness.rs | 5 +-- .../flowy-user/src/services/user_workspace.rs | 2 +- 37 files changed, 106 insertions(+), 72 deletions(-) diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index 3b498d310c45..6754aa787b5a 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -140,11 +140,12 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "appflowy-integrate" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "collab", "collab-database", + "collab-define", "collab-document", "collab-folder", "collab-persistence", @@ -729,7 +730,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "async-trait", @@ -748,13 +749,14 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "async-trait", "base64 0.21.2", "chrono", "collab", + "collab-define", "collab-derive", "collab-persistence", "collab-plugins", @@ -777,7 +779,7 @@ dependencies = [ [[package]] name = "collab-define" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "collab", @@ -789,7 +791,7 @@ dependencies = [ [[package]] name = "collab-derive" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "proc-macro2", "quote", @@ -801,7 +803,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "collab", @@ -821,7 +823,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "chrono", @@ -841,7 +843,7 @@ dependencies = [ [[package]] name = "collab-persistence" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "async-trait", "bincode", @@ -862,7 +864,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "async-trait", @@ -891,7 +893,7 @@ dependencies = [ [[package]] name = "collab-sync-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "bytes", "collab", @@ -905,7 +907,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "collab", @@ -921,7 +923,7 @@ dependencies = [ [[package]] name = "collab-ws" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "bytes", "collab-sync-protocol", @@ -1556,6 +1558,7 @@ version = "0.1.0" dependencies = [ "appflowy-integrate", "bytes", + "collab-define", "diesel", "flowy-config", "flowy-database-deps", @@ -1591,7 +1594,7 @@ name = "flowy-database-deps" version = "0.1.0" dependencies = [ "anyhow", - "collab-plugins", + "collab-define", "flowy-error", "lib-infra", ] @@ -1609,6 +1612,7 @@ dependencies = [ "chrono-tz 0.8.2", "collab", "collab-database", + "collab-define", "csv", "dashmap", "fancy-regex 0.10.0", @@ -1672,6 +1676,7 @@ dependencies = [ "appflowy-integrate", "bytes", "collab", + "collab-define", "collab-document", "flowy-codegen", "flowy-derive", @@ -1750,6 +1755,7 @@ dependencies = [ "bytes", "chrono", "collab", + "collab-define", "collab-folder", "flowy-codegen", "flowy-derive", @@ -1803,6 +1809,7 @@ dependencies = [ "bytes", "chrono", "collab", + "collab-define", "collab-document", "collab-plugins", "config", @@ -1900,6 +1907,7 @@ dependencies = [ "chrono", "collab", "collab-database", + "collab-define", "collab-document", "collab-folder", "collab-user", diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index b457a7d3f54d..3fc8b474c69b 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -34,15 +34,15 @@ default = ["custom-protocol"] custom-protocol = ["tauri/custom-protocol"] [patch.crates-io] -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } #collab = { path = "../../../../AppFlowy-Collab/collab" } #collab-folder = { path = "../../../../AppFlowy-Collab/collab-folder" } diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index eaa3705e0c77..374ebae20b43 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -120,11 +120,12 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "appflowy-integrate" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "collab", "collab-database", + "collab-define", "collab-document", "collab-folder", "collab-persistence", @@ -612,7 +613,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "async-trait", @@ -631,13 +632,14 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "async-trait", "base64 0.21.2", "chrono", "collab", + "collab-define", "collab-derive", "collab-persistence", "collab-plugins", @@ -660,7 +662,7 @@ dependencies = [ [[package]] name = "collab-define" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "collab", @@ -672,7 +674,7 @@ dependencies = [ [[package]] name = "collab-derive" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "proc-macro2", "quote", @@ -684,7 +686,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "collab", @@ -704,7 +706,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "chrono", @@ -724,7 +726,7 @@ dependencies = [ [[package]] name = "collab-persistence" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "async-trait", "bincode", @@ -745,7 +747,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "async-trait", @@ -774,7 +776,7 @@ dependencies = [ [[package]] name = "collab-sync-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "bytes", "collab", @@ -788,7 +790,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "anyhow", "collab", @@ -804,7 +806,7 @@ dependencies = [ [[package]] name = "collab-ws" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=eaa9844#eaa9844c17bd64b2ef00c26245b5cb44756dda4c" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=9bb9a7#9bb9a7e33c17677ec6f553fb8b98f66d6c9b6c2e" dependencies = [ "bytes", "collab-sync-protocol", @@ -1342,6 +1344,7 @@ version = "0.1.0" dependencies = [ "appflowy-integrate", "bytes", + "collab-define", "console-subscriber", "diesel", "flowy-config", @@ -1378,7 +1381,7 @@ name = "flowy-database-deps" version = "0.1.0" dependencies = [ "anyhow", - "collab-plugins", + "collab-define", "flowy-error", "lib-infra", ] @@ -1396,6 +1399,7 @@ dependencies = [ "chrono-tz 0.8.2", "collab", "collab-database", + "collab-define", "csv", "dashmap", "fancy-regex 0.10.0", @@ -1460,6 +1464,7 @@ dependencies = [ "appflowy-integrate", "bytes", "collab", + "collab-define", "collab-document", "flowy-codegen", "flowy-derive", @@ -1541,6 +1546,7 @@ dependencies = [ "bytes", "chrono", "collab", + "collab-define", "collab-folder", "flowy-codegen", "flowy-derive", @@ -1597,6 +1603,7 @@ dependencies = [ "bytes", "chrono", "collab", + "collab-define", "collab-document", "collab-plugins", "config", @@ -1747,6 +1754,7 @@ dependencies = [ "chrono", "collab", "collab-database", + "collab-define", "collab-document", "collab-folder", "collab-user", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 6e3047a04d57..6d21ec064140 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -49,14 +49,14 @@ lto = false incremental = false [patch.crates-io] -collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } -collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "eaa9844" } +collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-plugins = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-user = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } +collab-define = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "9bb9a7" } #collab = { path = "../AppFlowy-Collab/collab" } #collab-folder = { path = "../AppFlowy-Collab/collab-folder" } diff --git a/frontend/rust-lib/flowy-core/Cargo.toml b/frontend/rust-lib/flowy-core/Cargo.toml index 0ede6e006679..cf5f1347ad03 100644 --- a/frontend/rust-lib/flowy-core/Cargo.toml +++ b/frontend/rust-lib/flowy-core/Cargo.toml @@ -24,6 +24,7 @@ flowy-server = { path = "../flowy-server" } flowy-server-config = { path = "../flowy-server-config" } flowy-config = { path = "../flowy-config" } appflowy-integrate = { version = "0.1.0", features = ["postgres_storage_plugin", "snapshot_plugin"] } +collab-define = { version = "0.1.0" } diesel = { version = "1.4.8", features = ["sqlite"] } uuid = { version = "1.3.3", features = ["v4"] } flowy-storage = { path = "../flowy-storage" } diff --git a/frontend/rust-lib/flowy-core/src/integrate/server.rs b/frontend/rust-lib/flowy-core/src/integrate/server.rs index 708d3ff36a0c..512691122a34 100644 --- a/frontend/rust-lib/flowy-core/src/integrate/server.rs +++ b/frontend/rust-lib/flowy-core/src/integrate/server.rs @@ -3,8 +3,9 @@ use std::fmt::{Display, Formatter}; use std::sync::{Arc, Weak}; use appflowy_integrate::collab_builder::{CollabStorageProvider, CollabStorageType}; -use appflowy_integrate::{CollabObject, CollabType, RemoteCollabStorage, YrsDocAction}; +use appflowy_integrate::{RemoteCollabStorage, YrsDocAction}; use bytes::Bytes; +use collab_define::{CollabObject, CollabType}; use parking_lot::RwLock; use serde_repr::*; diff --git a/frontend/rust-lib/flowy-database-deps/Cargo.toml b/frontend/rust-lib/flowy-database-deps/Cargo.toml index 4ecdc868288a..115c54803724 100644 --- a/frontend/rust-lib/flowy-database-deps/Cargo.toml +++ b/frontend/rust-lib/flowy-database-deps/Cargo.toml @@ -8,5 +8,5 @@ edition = "2021" [dependencies] lib-infra = { path = "../../../shared-lib/lib-infra" } flowy-error = { path = "../flowy-error" } -collab-plugins = { version = "0.1.0" } +collab-define = { version = "0.1.0" } anyhow = "1.0.71" \ No newline at end of file diff --git a/frontend/rust-lib/flowy-database-deps/src/cloud.rs b/frontend/rust-lib/flowy-database-deps/src/cloud.rs index d6cf61e64960..25cc8b014a05 100644 --- a/frontend/rust-lib/flowy-database-deps/src/cloud.rs +++ b/frontend/rust-lib/flowy-database-deps/src/cloud.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use anyhow::Error; -use collab_plugins::cloud_storage::CollabType; +use collab_define::CollabType; use lib_infra::future::FutureResult; diff --git a/frontend/rust-lib/flowy-database2/Cargo.toml b/frontend/rust-lib/flowy-database2/Cargo.toml index c7c51d6a607d..e1c9334e3344 100644 --- a/frontend/rust-lib/flowy-database2/Cargo.toml +++ b/frontend/rust-lib/flowy-database2/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] collab = { version = "0.1.0" } collab-database = { version = "0.1.0" } +collab-define = { version = "0.1.0" } appflowy-integrate = {version = "0.1.0" } flowy-database-deps = { path = "../flowy-database-deps" } diff --git a/frontend/rust-lib/flowy-database2/src/manager.rs b/frontend/rust-lib/flowy-database2/src/manager.rs index 8fbdf4eced8f..ef3ec7eb9a5c 100644 --- a/frontend/rust-lib/flowy-database2/src/manager.rs +++ b/frontend/rust-lib/flowy-database2/src/manager.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::sync::{Arc, Weak}; use appflowy_integrate::collab_builder::AppFlowyCollabBuilder; -use appflowy_integrate::{CollabPersistenceConfig, CollabType, RocksCollabDB}; +use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB}; use collab::core::collab::{CollabRawData, MutexCollab}; use collab_database::blocks::BlockEvent; use collab_database::database::{DatabaseData, YrsDocAction}; @@ -12,6 +12,7 @@ use collab_database::user::{ WorkspaceDatabase, }; use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLayout}; +use collab_define::CollabType; use tokio::sync::RwLock; use flowy_database_deps::cloud::DatabaseCloudService; diff --git a/frontend/rust-lib/flowy-document2/Cargo.toml b/frontend/rust-lib/flowy-document2/Cargo.toml index 266ec7e5bb92..0c809c2fd2ff 100644 --- a/frontend/rust-lib/flowy-document2/Cargo.toml +++ b/frontend/rust-lib/flowy-document2/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] collab = { version = "0.1.0" } collab-document = { version = "0.1.0" } +collab-define = { version = "0.1.0" } appflowy-integrate = {version = "0.1.0" } flowy-document-deps = { path = "../flowy-document-deps" } flowy-storage = { path = "../flowy-storage" } diff --git a/frontend/rust-lib/flowy-document2/src/manager.rs b/frontend/rust-lib/flowy-document2/src/manager.rs index f18fd608cebb..c2de568778e2 100644 --- a/frontend/rust-lib/flowy-document2/src/manager.rs +++ b/frontend/rust-lib/flowy-document2/src/manager.rs @@ -2,8 +2,9 @@ use std::sync::Weak; use std::{collections::HashMap, sync::Arc}; use appflowy_integrate::collab_builder::AppFlowyCollabBuilder; -use appflowy_integrate::{CollabType, RocksCollabDB}; +use appflowy_integrate::RocksCollabDB; use collab::core::collab::MutexCollab; +use collab_define::CollabType; use collab_document::blocks::DocumentData; use collab_document::document::Document; use collab_document::document_data::default_document_data; diff --git a/frontend/rust-lib/flowy-folder2/Cargo.toml b/frontend/rust-lib/flowy-folder2/Cargo.toml index 89d339e08a93..ccf90ea28c3c 100644 --- a/frontend/rust-lib/flowy-folder2/Cargo.toml +++ b/frontend/rust-lib/flowy-folder2/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] collab = { version = "0.1.0" } collab-folder = { version = "0.1.0" } +collab-define = { version = "0.1.0" } appflowy-integrate = {version = "0.1.0" } flowy-folder-deps = { path = "../flowy-folder-deps" } diff --git a/frontend/rust-lib/flowy-folder2/src/manager.rs b/frontend/rust-lib/flowy-folder2/src/manager.rs index fb71cb990a4f..8cc54eb71620 100644 --- a/frontend/rust-lib/flowy-folder2/src/manager.rs +++ b/frontend/rust-lib/flowy-folder2/src/manager.rs @@ -3,9 +3,10 @@ use std::ops::Deref; use std::sync::{Arc, Weak}; use appflowy_integrate::collab_builder::AppFlowyCollabBuilder; -use appflowy_integrate::{CollabPersistenceConfig, CollabType, RocksCollabDB, YrsDocAction}; +use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB, YrsDocAction}; use collab::core::collab::{CollabRawData, MutexCollab}; use collab::core::collab_state::SyncState; +use collab_define::CollabType; use collab_folder::core::{ FavoritesInfo, Folder, FolderData, FolderNotify, TrashChange, TrashChangeReceiver, TrashInfo, View, ViewChange, ViewChangeReceiver, ViewLayout, ViewUpdate, Workspace, diff --git a/frontend/rust-lib/flowy-server/Cargo.toml b/frontend/rust-lib/flowy-server/Cargo.toml index b3a4fbfe5615..7861a2b8201b 100644 --- a/frontend/rust-lib/flowy-server/Cargo.toml +++ b/frontend/rust-lib/flowy-server/Cargo.toml @@ -27,6 +27,7 @@ chrono = { version = "0.4.22", default-features = false, features = ["clock"] } collab = { version = "0.1.0" } collab-plugins = { version = "0.1.0" } collab-document = { version = "0.1.0" } +collab-define = { version = "0.1.0" } hex = "0.4.3" postgrest = "1.0" lib-infra = { path = "../../../shared-lib/lib-infra" } diff --git a/frontend/rust-lib/flowy-server/src/af_cloud/impls/database.rs b/frontend/rust-lib/flowy-server/src/af_cloud/impls/database.rs index fa3905d0116b..d6494d9126a6 100644 --- a/frontend/rust-lib/flowy-server/src/af_cloud/impls/database.rs +++ b/frontend/rust-lib/flowy-server/src/af_cloud/impls/database.rs @@ -1,5 +1,5 @@ use anyhow::Error; -use collab_plugins::cloud_storage::CollabType; +use collab_define::CollabType; use flowy_database_deps::cloud::{ CollabObjectUpdate, CollabObjectUpdateByOid, DatabaseCloudService, DatabaseSnapshot, diff --git a/frontend/rust-lib/flowy-server/src/af_cloud/impls/user.rs b/frontend/rust-lib/flowy-server/src/af_cloud/impls/user.rs index fe92ea68b079..510e77695362 100644 --- a/frontend/rust-lib/flowy-server/src/af_cloud/impls/user.rs +++ b/frontend/rust-lib/flowy-server/src/af_cloud/impls/user.rs @@ -1,5 +1,5 @@ use anyhow::Error; -use collab_plugins::cloud_storage::CollabObject; +use collab_define::CollabObject; use flowy_error::{ErrorCode, FlowyError}; use flowy_user_deps::cloud::UserCloudService; diff --git a/frontend/rust-lib/flowy-server/src/af_cloud/server.rs b/frontend/rust-lib/flowy-server/src/af_cloud/server.rs index d1e1734025fa..324b3f92a2bf 100644 --- a/frontend/rust-lib/flowy-server/src/af_cloud/server.rs +++ b/frontend/rust-lib/flowy-server/src/af_cloud/server.rs @@ -1,6 +1,7 @@ use std::sync::Arc; -use collab_plugins::cloud_storage::{CollabObject, RemoteCollabStorage}; +use collab_define::CollabObject; +use collab_plugins::cloud_storage::RemoteCollabStorage; use flowy_database_deps::cloud::DatabaseCloudService; use flowy_document_deps::cloud::DocumentCloudService; diff --git a/frontend/rust-lib/flowy-server/src/local_server/impls/database.rs b/frontend/rust-lib/flowy-server/src/local_server/impls/database.rs index 29eae8542d17..a8d9958f9639 100644 --- a/frontend/rust-lib/flowy-server/src/local_server/impls/database.rs +++ b/frontend/rust-lib/flowy-server/src/local_server/impls/database.rs @@ -1,5 +1,5 @@ use anyhow::Error; -use collab_plugins::cloud_storage::CollabType; +use collab_define::CollabType; use flowy_database_deps::cloud::{ CollabObjectUpdate, CollabObjectUpdateByOid, DatabaseCloudService, DatabaseSnapshot, diff --git a/frontend/rust-lib/flowy-server/src/local_server/impls/user.rs b/frontend/rust-lib/flowy-server/src/local_server/impls/user.rs index cb15d1a644da..12a64952fb74 100644 --- a/frontend/rust-lib/flowy-server/src/local_server/impls/user.rs +++ b/frontend/rust-lib/flowy-server/src/local_server/impls/user.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use anyhow::Error; -use collab_plugins::cloud_storage::CollabObject; +use collab_define::CollabObject; use lazy_static::lazy_static; use parking_lot::Mutex; diff --git a/frontend/rust-lib/flowy-server/src/local_server/server.rs b/frontend/rust-lib/flowy-server/src/local_server/server.rs index 3bbe80d434df..d941a13e06ea 100644 --- a/frontend/rust-lib/flowy-server/src/local_server/server.rs +++ b/frontend/rust-lib/flowy-server/src/local_server/server.rs @@ -1,6 +1,7 @@ use std::sync::Arc; -use collab_plugins::cloud_storage::{CollabObject, RemoteCollabStorage}; +use collab_define::CollabObject; +use collab_plugins::cloud_storage::RemoteCollabStorage; use parking_lot::RwLock; use tokio::sync::mpsc; diff --git a/frontend/rust-lib/flowy-server/src/server.rs b/frontend/rust-lib/flowy-server/src/server.rs index 0234599c4dc1..b301f507e6e7 100644 --- a/frontend/rust-lib/flowy-server/src/server.rs +++ b/frontend/rust-lib/flowy-server/src/server.rs @@ -1,6 +1,7 @@ use std::sync::Arc; -use collab_plugins::cloud_storage::{CollabObject, RemoteCollabStorage}; +use collab_define::CollabObject; +use collab_plugins::cloud_storage::RemoteCollabStorage; use parking_lot::RwLock; use flowy_database_deps::cloud::DatabaseCloudService; diff --git a/frontend/rust-lib/flowy-server/src/supabase/api/collab_storage.rs b/frontend/rust-lib/flowy-server/src/supabase/api/collab_storage.rs index 4fa4081a5e97..bd68b45c01b6 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/api/collab_storage.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/api/collab_storage.rs @@ -4,9 +4,9 @@ use std::sync::{Arc, Weak}; use anyhow::Error; use chrono::{DateTime, Utc}; use collab::preclude::merge_updates_v1; +use collab_define::CollabObject; use collab_plugins::cloud_storage::{ - CollabObject, MsgId, RemoteCollabSnapshot, RemoteCollabState, RemoteCollabStorage, - RemoteUpdateReceiver, + MsgId, RemoteCollabSnapshot, RemoteCollabState, RemoteCollabStorage, RemoteUpdateReceiver, }; use parking_lot::Mutex; use tokio::task::spawn_blocking; diff --git a/frontend/rust-lib/flowy-server/src/supabase/api/database.rs b/frontend/rust-lib/flowy-server/src/supabase/api/database.rs index 4e0546fbd6cd..57d5be273e80 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/api/database.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/api/database.rs @@ -1,5 +1,5 @@ use anyhow::Error; -use collab_plugins::cloud_storage::CollabType; +use collab_define::CollabType; use tokio::sync::oneshot::channel; use flowy_database_deps::cloud::{ diff --git a/frontend/rust-lib/flowy-server/src/supabase/api/document.rs b/frontend/rust-lib/flowy-server/src/supabase/api/document.rs index 85a74d0109ea..a64ba759b908 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/api/document.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/api/document.rs @@ -1,8 +1,8 @@ use anyhow::Error; use collab::core::origin::CollabOrigin; +use collab_define::CollabType; use collab_document::blocks::DocumentData; use collab_document::document::Document; -use collab_plugins::cloud_storage::CollabType; use tokio::sync::oneshot::channel; use flowy_document_deps::cloud::{DocumentCloudService, DocumentSnapshot}; diff --git a/frontend/rust-lib/flowy-server/src/supabase/api/folder.rs b/frontend/rust-lib/flowy-server/src/supabase/api/folder.rs index 5da921c5223a..d6726d8b44ef 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/api/folder.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/api/folder.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use anyhow::Error; use chrono::{DateTime, Utc}; use collab::core::origin::CollabOrigin; -use collab_plugins::cloud_storage::CollabType; +use collab_define::CollabType; use serde_json::Value; use tokio::sync::oneshot::channel; diff --git a/frontend/rust-lib/flowy-server/src/supabase/api/request.rs b/frontend/rust-lib/flowy-server/src/supabase/api/request.rs index 11a493a096e9..c0eec4d74c0b 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/api/request.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/api/request.rs @@ -7,7 +7,8 @@ use std::time::Duration; use anyhow::Error; use chrono::{DateTime, Utc}; -use collab_plugins::cloud_storage::{CollabObject, CollabType, RemoteCollabSnapshot}; +use collab_define::{CollabObject, CollabType}; +use collab_plugins::cloud_storage::RemoteCollabSnapshot; use serde_json::Value; use tokio_retry::strategy::FixedInterval; use tokio_retry::{Action, Condition, RetryIf}; diff --git a/frontend/rust-lib/flowy-server/src/supabase/api/user.rs b/frontend/rust-lib/flowy-server/src/supabase/api/user.rs index 22a0dddbb74d..c07b17e6f4e7 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/api/user.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/api/user.rs @@ -8,7 +8,7 @@ use std::time::Duration; use anyhow::Error; use collab::core::collab::MutexCollab; use collab::core::origin::CollabOrigin; -use collab_plugins::cloud_storage::CollabObject; +use collab_define::{CollabObject, CollabType}; use parking_lot::RwLock; use serde_json::Value; use tokio::sync::oneshot::channel; diff --git a/frontend/rust-lib/flowy-server/src/supabase/define.rs b/frontend/rust-lib/flowy-server/src/supabase/define.rs index c4f5525fc23d..31b4e097f551 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/define.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/define.rs @@ -1,4 +1,4 @@ -pub use collab_plugins::cloud_storage::CollabType; +use collab_define::CollabType; pub const AF_COLLAB_UPDATE_TABLE: &str = "af_collab_update"; pub const AF_COLLAB_KEY_COLUMN: &str = "key"; diff --git a/frontend/rust-lib/flowy-server/src/supabase/server.rs b/frontend/rust-lib/flowy-server/src/supabase/server.rs index 398c960d72ab..5b967bca929b 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/server.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/server.rs @@ -1,7 +1,8 @@ use std::collections::HashMap; use std::sync::{Arc, Weak}; -use collab_plugins::cloud_storage::{CollabObject, RemoteCollabStorage, RemoteUpdateSender}; +use collab_define::CollabObject; +use collab_plugins::cloud_storage::{RemoteCollabStorage, RemoteUpdateSender}; use parking_lot::RwLock; use flowy_database_deps::cloud::DatabaseCloudService; diff --git a/frontend/rust-lib/flowy-server/tests/supabase_test/database_test.rs b/frontend/rust-lib/flowy-server/tests/supabase_test/database_test.rs index 101b1c984fd8..2a2bf80c7a11 100644 --- a/frontend/rust-lib/flowy-server/tests/supabase_test/database_test.rs +++ b/frontend/rust-lib/flowy-server/tests/supabase_test/database_test.rs @@ -1,4 +1,4 @@ -use collab_plugins::cloud_storage::{CollabObject, CollabType}; +use collab_define::{CollabObject, CollabType}; use uuid::Uuid; use flowy_user_deps::entities::SignUpResponse; diff --git a/frontend/rust-lib/flowy-server/tests/supabase_test/folder_test.rs b/frontend/rust-lib/flowy-server/tests/supabase_test/folder_test.rs index 6586b520104a..03b054161843 100644 --- a/frontend/rust-lib/flowy-server/tests/supabase_test/folder_test.rs +++ b/frontend/rust-lib/flowy-server/tests/supabase_test/folder_test.rs @@ -1,5 +1,5 @@ use assert_json_diff::assert_json_eq; -use collab_plugins::cloud_storage::{CollabObject, CollabType}; +use collab_define::{CollabObject, CollabType}; use serde_json::json; use uuid::Uuid; use yrs::types::ToJson; diff --git a/frontend/rust-lib/flowy-user/Cargo.toml b/frontend/rust-lib/flowy-user/Cargo.toml index 52a6b561f379..adc25e250370 100644 --- a/frontend/rust-lib/flowy-user/Cargo.toml +++ b/frontend/rust-lib/flowy-user/Cargo.toml @@ -21,6 +21,7 @@ collab-folder = { version = "0.1.0" } collab-document = { version = "0.1.0" } collab-database = { version = "0.1.0" } collab-user = { version = "0.1.0" } +collab-define = { version = "0.1.0" } flowy-user-deps = { path = "../flowy-user-deps" } anyhow = "1.0.75" diff --git a/frontend/rust-lib/flowy-user/src/entities/reminder.rs b/frontend/rust-lib/flowy-user/src/entities/reminder.rs index 5539c4f495de..bc7eccd97de1 100644 --- a/frontend/rust-lib/flowy-user/src/entities/reminder.rs +++ b/frontend/rust-lib/flowy-user/src/entities/reminder.rs @@ -1,4 +1,5 @@ -use appflowy_integrate::reminder::{ObjectType, Reminder}; +use collab_define::reminder::{ObjectType, Reminder}; + use flowy_derive::ProtoBuf; #[derive(ProtoBuf, Default, Clone)] diff --git a/frontend/rust-lib/flowy-user/src/migrations/sync_new_user.rs b/frontend/rust-lib/flowy-user/src/migrations/sync_new_user.rs index 24886f40cd35..a1c1c0025245 100644 --- a/frontend/rust-lib/flowy-user/src/migrations/sync_new_user.rs +++ b/frontend/rust-lib/flowy-user/src/migrations/sync_new_user.rs @@ -4,12 +4,13 @@ use std::pin::Pin; use std::sync::Arc; use anyhow::{anyhow, Error}; -use appflowy_integrate::{CollabObject, CollabType, PersistenceError, RocksCollabDB, YrsDocAction}; +use appflowy_integrate::{PersistenceError, RocksCollabDB, YrsDocAction}; use collab::core::collab::MutexCollab; use collab::preclude::Collab; use collab_database::database::get_database_row_ids; use collab_database::rows::database_row_document_id_from_row_id; use collab_database::user::{get_database_with_views, DatabaseWithViews}; +use collab_define::{CollabObject, CollabType}; use collab_folder::core::{Folder, View, ViewLayout}; use parking_lot::Mutex; diff --git a/frontend/rust-lib/flowy-user/src/services/user_awareness.rs b/frontend/rust-lib/flowy-user/src/services/user_awareness.rs index 1c31831dcf95..f239f5b12810 100644 --- a/frontend/rust-lib/flowy-user/src/services/user_awareness.rs +++ b/frontend/rust-lib/flowy-user/src/services/user_awareness.rs @@ -1,8 +1,9 @@ use std::sync::{Arc, Weak}; -use appflowy_integrate::reminder::Reminder; -use appflowy_integrate::{CollabType, RocksCollabDB}; +use appflowy_integrate::RocksCollabDB; use collab::core::collab::{CollabRawData, MutexCollab}; +use collab_define::reminder::Reminder; +use collab_define::CollabType; use collab_user::core::{MutexUserAwareness, UserAwareness}; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; diff --git a/frontend/rust-lib/flowy-user/src/services/user_workspace.rs b/frontend/rust-lib/flowy-user/src/services/user_workspace.rs index 985e9685bf6f..03c493414f18 100644 --- a/frontend/rust-lib/flowy-user/src/services/user_workspace.rs +++ b/frontend/rust-lib/flowy-user/src/services/user_workspace.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use std::sync::Arc; -use appflowy_integrate::{CollabObject, CollabType}; +use collab_define::{CollabObject, CollabType}; use flowy_error::{FlowyError, FlowyResult}; use flowy_sqlite::schema::user_workspace_table; From d86531ac920525e91453e02daf2a008a0af7e6f9 Mon Sep 17 00:00:00 2001 From: Vincenzo De Petris <37916223+vincendep@users.noreply.github.com> Date: Thu, 14 Sep 2023 12:08:44 +0200 Subject: [PATCH 09/14] fix: add left padding to TabsManager widget (#3396) --- .../lib/workspace/presentation/home/home_stack.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart index c44027284309..885421096d80 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart @@ -44,7 +44,10 @@ class HomeStack extends StatelessWidget { return Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - TabsManager(pageController: pageController), + Padding( + padding: EdgeInsets.only(left: layout.menuSpacing), + child: TabsManager(pageController: pageController), + ), state.currentPageManager.stackTopBar(layout: layout), Expanded( child: PageView( From 16adacac38f47e7694bc344c93389bb38b5ba16a Mon Sep 17 00:00:00 2001 From: Generic-Ripe <144800764+Generic-Ripe@users.noreply.github.com> Date: Thu, 14 Sep 2023 12:47:55 +0200 Subject: [PATCH 10/14] chore: update sv.json (#3386) In this context board is tavla in Swedish. Styrelse is board of directors. --- frontend/resources/translations/sv.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/resources/translations/sv.json b/frontend/resources/translations/sv.json index 9844ae89ece8..f476c293b2d7 100644 --- a/frontend/resources/translations/sv.json +++ b/frontend/resources/translations/sv.json @@ -393,8 +393,8 @@ }, "slashMenu": { "board": { - "selectABoardToLinkTo": "Välj en styrelse att länka till", - "createANewBoard": "Skapa en ny styrelse" + "selectABoardToLinkTo": "Välj en tavla att länka till", + "createANewBoard": "Skapa en ny tavla" }, "grid": { "selectAGridToLinkTo": "Välj ett rutnät att länka till", @@ -409,7 +409,7 @@ "outline": "Skissera" }, "plugins": { - "referencedBoard": "Refererad styrelse", + "referencedBoard": "Refererad tavla", "referencedGrid": "Refererade rutnät", "referencedCalendar": "Refererad kalender", "autoGeneratorMenuItemName": "OpenAI Writer", @@ -528,7 +528,7 @@ "column": { "create_new_card": "Nytt" }, - "menuName": "Styrelse", + "menuName": "Tavla", "referencedBoardPrefix": "Utsikt över" }, "moreAction": { @@ -597,4 +597,4 @@ "deleteContentTitle": "Är du säker på att du vill ta bort {pageType}?", "deleteContentCaption": "om du tar bort denna {pageType} kan du återställa den från papperskorgen." } -} \ No newline at end of file +} From fe55452c79b0eb88202d6f36fb059d652f2ac467 Mon Sep 17 00:00:00 2001 From: Aman Negi <37607224+AmanNegi@users.noreply.github.com> Date: Thu, 14 Sep 2023 16:18:42 +0530 Subject: [PATCH 11/14] fix: restore banner theme issues (#3299) --- .../plugins/document/presentation/banner.dart | 20 +++++++++---------- .../lib/workspace/application/appearance.dart | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/banner.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/banner.dart index 49a76846a0c9..90acac0218a5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/banner.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/banner.dart @@ -17,11 +17,12 @@ class DocumentBanner extends StatelessWidget { @override Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; return ConstrainedBox( constraints: const BoxConstraints(minHeight: 60), child: Container( width: double.infinity, - color: Theme.of(context).colorScheme.primary, + color: colorScheme.error, child: FittedBox( alignment: Alignment.center, fit: BoxFit.scaleDown, @@ -29,7 +30,8 @@ class DocumentBanner extends StatelessWidget { children: [ FlowyText.medium( LocaleKeys.deletePagePrompt_text.tr(), - color: Colors.white, + color: colorScheme.onError, + fontSize: 14, ), const HSpace(20), BaseStyledButton( @@ -37,15 +39,14 @@ class DocumentBanner extends StatelessWidget { minHeight: 40, contentPadding: EdgeInsets.zero, bgColor: Colors.transparent, - hoverColor: Theme.of(context).colorScheme.primary, highlightColor: Theme.of(context).colorScheme.primaryContainer, - outlineColor: Colors.white, + outlineColor: colorScheme.onError, borderRadius: Corners.s8Border, onPressed: onRestore, child: FlowyText.medium( LocaleKeys.deletePagePrompt_restore.tr(), - color: Theme.of(context).colorScheme.onPrimary, - fontSize: 14, + color: colorScheme.onError, + fontSize: 13, ), ), const HSpace(20), @@ -54,15 +55,14 @@ class DocumentBanner extends StatelessWidget { minHeight: 40, contentPadding: EdgeInsets.zero, bgColor: Colors.transparent, - hoverColor: Theme.of(context).colorScheme.primaryContainer, highlightColor: Theme.of(context).colorScheme.primary, - outlineColor: Colors.white, + outlineColor: colorScheme.onError, borderRadius: Corners.s8Border, onPressed: onDelete, child: FlowyText.medium( LocaleKeys.deletePagePrompt_deletePermanent.tr(), - color: Theme.of(context).colorScheme.onPrimary, - fontSize: 14, + color: colorScheme.onError, + fontSize: 13, ), ), ], diff --git a/frontend/appflowy_flutter/lib/workspace/application/appearance.dart b/frontend/appflowy_flutter/lib/workspace/application/appearance.dart index d44fe125c0da..b46d47a6a969 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/appearance.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/appearance.dart @@ -341,7 +341,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState { onSurface: theme.hoverFG, // grey hover color inverseSurface: theme.hoverBG3, - onError: theme.shader7, + onError: theme.onPrimary, error: theme.red, outline: theme.shader4, surfaceVariant: theme.sidebarBg, From 26a2bffcd167a65b9a4f5d81e04b90ad67df36da Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Thu, 14 Sep 2023 19:22:32 +0800 Subject: [PATCH 12/14] fix: some UI issues were present in version 0.3.1. (#3401) --- .../sidebar/sidebar_test.dart | 8 +-- .../integration_test/util/base.dart | 29 +++++++- .../util/common_operations.dart | 12 +++- .../appflowy_flutter/lib/core/config/kv.dart | 28 ++++++++ .../lib/core/config/kv_keys.dart | 6 ++ .../editor_transaction_adapter.dart | 14 ++-- .../actions/block_action_option_button.dart | 6 +- .../align_toolbar_item.dart | 66 ++++++++++++------- .../outline/outline_block_component.dart | 14 ++-- .../toggle/toggle_block_component.dart | 5 +- .../settings/create_file_settings_cubit.dart | 29 ++++++++ .../application/settings/prelude.dart | 3 +- .../menu/sidebar/folder/personal_folder.dart | 16 ++--- .../home/menu/sidebar/rename_view_dialog.dart | 35 ++++++++++ .../menu/sidebar/sidebar_new_page_button.dart | 28 ++++---- .../home/menu/view/view_item.dart | 16 ++--- .../create_file_setting.dart | 37 +++++++++++ .../widgets/settings_appearance_view.dart | 2 + .../lib/widget/dialog/styled_dialogs.dart | 7 +- frontend/appflowy_flutter/pubspec.lock | 12 +++- frontend/appflowy_flutter/pubspec.yaml | 2 +- 21 files changed, 292 insertions(+), 83 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/workspace/application/settings/create_file_settings_cubit.dart create mode 100644 frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart create mode 100644 frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/create_file_setting.dart diff --git a/frontend/appflowy_flutter/integration_test/sidebar/sidebar_test.dart b/frontend/appflowy_flutter/integration_test/sidebar/sidebar_test.dart index bbad6d833b4e..b416c73138a9 100644 --- a/frontend/appflowy_flutter/integration_test/sidebar/sidebar_test.dart +++ b/frontend/appflowy_flutter/integration_test/sidebar/sidebar_test.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart'; import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart'; @@ -7,7 +8,7 @@ import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -22,14 +23,11 @@ void main() { await tester.tapGoButton(); // create a new page - const name = 'Hello AppFlowy'; await tester.tapNewPageButton(); - await tester.enterText(find.byType(TextFormField), name); - await tester.tapOKButton(); // expect to see a new document tester.expectToSeePageName( - name, + LocaleKeys.menuAppHeader_defaultNewPageName.tr(), ); // and with one paragraph block expect(find.byType(TextBlockComponentWidget), findsOneWidget); diff --git a/frontend/appflowy_flutter/integration_test/util/base.dart b/frontend/appflowy_flutter/integration_test/util/base.dart index 27ca2d529647..bfe6a8d833f0 100644 --- a/frontend/appflowy_flutter/integration_test/util/base.dart +++ b/frontend/appflowy_flutter/integration_test/util/base.dart @@ -1,7 +1,9 @@ +import 'dart:async'; import 'dart:io'; import 'package:appflowy/startup/entry_point.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/user/presentation/presentation.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:flowy_infra/uuid.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -9,8 +11,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; class FlowyTestContext { FlowyTestContext({ @@ -39,8 +41,8 @@ extension AppFlowyTestBase on WidgetTester { IntegrationMode.integrationTest, ); - await wait(3000); - await pumpAndSettle(const Duration(seconds: 2)); + await waitUntilSignInPageShow(); + return FlowyTestContext( applicationDataDirectory: directory, ); @@ -78,6 +80,27 @@ extension AppFlowyTestBase on WidgetTester { return directory.path; } + Future waitUntilSignInPageShow() async { + final finder = find.byType(GoButton); + await pumpUntilFound(finder); + expect(finder, findsOneWidget); + } + + Future pumpUntilFound( + Finder finder, { + Duration timeout = const Duration(seconds: 10), + }) async { + bool timerDone = false; + final timer = Timer(timeout, () => timerDone = true); + while (timerDone != true) { + await pump(); + if (any(finder)) { + timerDone = true; + } + } + timer.cancel(); + } + Future tapButton( Finder finder, { int? pointer, diff --git a/frontend/appflowy_flutter/integration_test/util/common_operations.dart b/frontend/appflowy_flutter/integration_test/util/common_operations.dart index 11bf8fef5187..10cd706489da 100644 --- a/frontend/appflowy_flutter/integration_test/util/common_operations.dart +++ b/frontend/appflowy_flutter/integration_test/util/common_operations.dart @@ -1,6 +1,9 @@ +import 'package:appflowy/core/config/kv.dart'; +import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/share/share_button.dart'; +import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/presentation/screens/screens.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart'; @@ -279,7 +282,14 @@ extension CommonOperations on WidgetTester { // create a new page await tapAddViewButton(name: parentName ?? gettingStarted); await tapButtonWithName(layout.menuName); - await tapOKButton(); + final settingsOrFailure = await getIt().getWithFormat( + KVKeys.showRenameDialogWhenCreatingNewFile, + (value) => bool.parse(value), + ); + final showRenameDialog = settingsOrFailure.fold((l) => false, (r) => r); + if (showRenameDialog) { + await tapOKButton(); + } await pumpAndSettle(); // hover on it and change it's name diff --git a/frontend/appflowy_flutter/lib/core/config/kv.dart b/frontend/appflowy_flutter/lib/core/config/kv.dart index 8ed337de51bc..141041475ac3 100644 --- a/frontend/appflowy_flutter/lib/core/config/kv.dart +++ b/frontend/appflowy_flutter/lib/core/config/kv.dart @@ -7,6 +7,10 @@ import 'package:shared_preferences/shared_preferences.dart'; abstract class KeyValueStorage { Future set(String key, String value); Future> get(String key); + Future> getWithFormat( + String key, + T Function(String value) formatter, + ); Future remove(String key); Future clear(); } @@ -26,6 +30,18 @@ class DartKeyValue implements KeyValueStorage { return Left(FlowyError()); } + @override + Future> getWithFormat( + String key, + T Function(String value) formatter, + ) async { + final value = await get(key); + return value.fold( + (l) => left(l), + (r) => right(formatter(r)), + ); + } + @override Future remove(String key) async { await _initSharedPreferencesIfNeeded(); @@ -71,6 +87,18 @@ class RustKeyValue implements KeyValueStorage { return response.swap().map((r) => r.value); } + @override + Future> getWithFormat( + String key, + T Function(String value) formatter, + ) async { + final value = await get(key); + return value.fold( + (l) => left(l), + (r) => right(formatter(r)), + ); + } + @override Future remove(String key) async { await ConfigEventRemoveKeyValue( diff --git a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart index 2d32c2c10ba4..72b0988d5b0b 100644 --- a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart +++ b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart @@ -43,4 +43,10 @@ class KVKeys { /// The value is a json string with the following format: /// {'SidebarFolderCategoryType.value': true} static const String expandedFolders = 'expandedFolders'; + + /// The key for saving if showing the rename dialog when creating a new file + /// + /// The value is a boolean string. + static const String showRenameDialogWhenCreatingNewFile = + 'showRenameDialogWhenCreatingNewFile'; } diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart b/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart index 873a35527ebb..bf7ed031ffca 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart @@ -34,11 +34,13 @@ class TransactionAdapter { final DocumentService documentService; final String documentId; - final bool _enableDebug = false; + final bool _enableDebug = true; Future apply(Transaction transaction, EditorState editorState) async { final stopwatch = Stopwatch()..start(); - Log.debug('transaction => ${transaction.toJson()}'); + if (_enableDebug) { + Log.debug('transaction => ${transaction.toJson()}'); + } final actions = transaction.operations .map((op) => op.toBlockAction(editorState, documentId)) .whereNotNull() @@ -58,14 +60,18 @@ class TransactionAdapter { textId: payload.textId, delta: payload.delta, ); - Log.debug('create external text: ${payload.delta}'); + if (_enableDebug) { + Log.debug('create external text: ${payload.delta}'); + } } else if (type == TextDeltaType.update) { await documentService.updateExternalText( documentId: payload.documentId, textId: payload.textId, delta: payload.delta, ); - Log.debug('update external text: ${payload.delta}'); + if (_enableDebug) { + Log.debug('update external text: ${payload.delta}'); + } } } final blockActions = diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart index 03997dd69957..9042d885ac48 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart @@ -40,12 +40,16 @@ class BlockOptionButton extends StatelessWidget { return PopoverActionList( direction: PopoverDirection.leftWithCenterAligned, actions: popoverActions, - onPopupBuilder: () => blockComponentState.alwaysShowActions = true, + onPopupBuilder: () { + keepEditorFocusNotifier.value += 1; + blockComponentState.alwaysShowActions = true; + }, onClosed: () { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { editorState.selectionType = null; editorState.selection = null; blockComponentState.alwaysShowActions = false; + keepEditorFocusNotifier.value -= 1; }); }, onSelected: (action, controller) { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart index 6d69602851b0..7ed136bc5194 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart @@ -1,6 +1,8 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -31,24 +33,31 @@ final alignToolbarItem = ToolbarItem( data = FlowySvgs.toolbar_align_right_s; } - final child = FlowySvg( - data, - size: const Size.square(16), - color: isHighlight ? highlightColor : Colors.white, + final child = MouseRegion( + cursor: SystemMouseCursors.click, + child: FlowySvg( + data, + size: const Size.square(20), + color: isHighlight ? highlightColor : Colors.white, + ), ); - return _AlignmentButtons( - child: child, - onAlignChanged: (align) async { - await editorState.updateNode( - selection, - (node) => node.copyWith( - attributes: { - ...node.attributes, - blockComponentAlign: align, - }, - ), - ); - }, + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: _AlignmentButtons( + child: child, + onAlignChanged: (align) async { + await editorState.updateNode( + selection, + (node) => node.copyWith( + attributes: { + ...node.attributes, + blockComponentAlign: align, + }, + ), + ); + }, + ), ); }, ); @@ -71,11 +80,15 @@ class _AlignmentButtonsState extends State<_AlignmentButtons> { Widget build(BuildContext context) { return AppFlowyPopover( windowPadding: const EdgeInsets.all(0), - margin: const EdgeInsets.all(0), + margin: const EdgeInsets.all(4), direction: PopoverDirection.bottomWithCenterAligned, offset: const Offset(0, 10), - child: widget.child, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.onTertiary, + borderRadius: const BorderRadius.all(Radius.circular(4)), + ), popupBuilder: (_) => _AlignButtons(onAlignChanged: widget.onAlignChanged), + child: widget.child, ); } } @@ -97,16 +110,19 @@ class _AlignButtons extends StatelessWidget { const HSpace(4), _AlignButton( icon: FlowySvgs.toolbar_align_left_s, + tooltips: LocaleKeys.document_plugins_optionAction_left.tr(), onTap: () => onAlignChanged('left'), ), const _Divider(), _AlignButton( icon: FlowySvgs.toolbar_align_center_s, + tooltips: LocaleKeys.document_plugins_optionAction_center.tr(), onTap: () => onAlignChanged('center'), ), const _Divider(), _AlignButton( icon: FlowySvgs.toolbar_align_right_s, + tooltips: LocaleKeys.document_plugins_optionAction_right.tr(), onTap: () => onAlignChanged('right'), ), const HSpace(4), @@ -119,19 +135,25 @@ class _AlignButtons extends StatelessWidget { class _AlignButton extends StatelessWidget { const _AlignButton({ required this.icon, + required this.tooltips, required this.onTap, }); final FlowySvgData icon; + final String tooltips; final VoidCallback onTap; @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, - child: FlowySvg( - icon, - size: const Size.square(16), + child: Tooltip( + message: tooltips, + child: FlowySvg( + icon, + size: const Size.square(16), + color: Colors.white, + ), ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart index b1ad9e172e7b..63400288c45b 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart @@ -168,7 +168,7 @@ class OutlineItemWidget extends StatelessWidget { hoverColor: Theme.of(context).hoverColor, ), child: GestureDetector( - onTap: () => updateBlockSelection(context), + onTap: () => scrollToBlock(context), child: Container( padding: EdgeInsets.only(left: node.leftIndent), child: Text( @@ -180,14 +180,14 @@ class OutlineItemWidget extends StatelessWidget { ); } - void updateBlockSelection(BuildContext context) async { + void scrollToBlock(BuildContext context) { final editorState = context.read(); - editorState.selectionType = SelectionType.block; - editorState.selection = Selection.collapsed( - Position(path: node.path, offset: node.delta?.length ?? 0), - ); + final editorScrollController = context.read(); + editorScrollController.itemScrollController.jumpTo(index: node.path.first); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - editorState.selectionType = null; + editorState.selection = Selection.collapsed( + Position(path: node.path, offset: node.delta?.length ?? 0), + ); }); } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart index acdde7936ba8..3c8a5145658e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart @@ -146,7 +146,10 @@ class _ToggleListBlockComponentWidgetState } @override - Widget buildComponent(BuildContext context) { + Widget buildComponent( + BuildContext context, { + bool withBackgroundColor = false, + }) { final textDirection = calculateTextDirection( layoutDirection: Directionality.maybeOf(context), ); diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/create_file_settings_cubit.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/create_file_settings_cubit.dart new file mode 100644 index 000000000000..564fea5a5192 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/create_file_settings_cubit.dart @@ -0,0 +1,29 @@ +import 'package:appflowy/core/config/kv.dart'; +import 'package:appflowy/core/config/kv_keys.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CreateFileSettingsCubit extends Cubit { + CreateFileSettingsCubit(super.initialState) { + getInitialSettings(); + } + + Future toggle({bool? value}) async { + await getIt().set( + KVKeys.showRenameDialogWhenCreatingNewFile, + (value ?? !state).toString(), + ); + emit(value ?? !state); + } + + Future getInitialSettings() async { + final settingsOrFailure = await getIt().getWithFormat( + KVKeys.showRenameDialogWhenCreatingNewFile, + (value) => bool.parse(value), + ); + settingsOrFailure.fold( + (_) => emit(false), + (settings) => emit(settings), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/prelude.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/prelude.dart index e2923d42a288..926ff91788b8 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/prelude.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/prelude.dart @@ -1,2 +1,3 @@ -export 'settings_dialog_bloc.dart'; export 'application_data_storage.dart'; +export 'create_file_settings_cubit.dart'; +export 'settings_dialog_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/folder/personal_folder.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/folder/personal_folder.dart index e4258d9fe69e..0b07ceb12b73 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/folder/personal_folder.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/folder/personal_folder.dart @@ -4,8 +4,8 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/menu/menu_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -118,14 +118,14 @@ class _PersonalFolderHeaderState extends State { width: iconSize, icon: const FlowySvg(FlowySvgs.add_s), onPressed: () { - NavigatorTextFieldDialog( - title: LocaleKeys.newPageText.tr(), - value: '', - confirm: (value) { - if (value.isNotEmpty) { + createViewAndShowRenameDialogIfNeeded( + context, + LocaleKeys.newPageText.tr(), + (viewName) { + if (viewName.isNotEmpty) { context.read().add( MenuEvent.createApp( - value, + viewName, index: 0, ), ); @@ -133,7 +133,7 @@ class _PersonalFolderHeaderState extends State { widget.onAdded(); } }, - ).show(context); + ); }, ), ] diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart new file mode 100644 index 000000000000..3856ce02f7f7 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart @@ -0,0 +1,35 @@ +import 'package:appflowy/core/config/kv.dart'; +import 'package:appflowy/core/config/kv_keys.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +/// Creates a new view and shows the rename dialog if needed. +/// +/// If the user has enabled the setting to show the rename dialog when creating a new view, +/// this function will show the rename dialog. +/// +/// Otherwise, it will just create the view with default name. +Future createViewAndShowRenameDialogIfNeeded( + BuildContext context, + String dialogTitle, + void Function(String viewName) createView, +) async { + final value = await getIt().getWithFormat( + KVKeys.showRenameDialogWhenCreatingNewFile, + (value) => bool.parse(value), + ); + final showRenameDialog = value.fold((l) => false, (r) => r); + if (context.mounted && showRenameDialog) { + NavigatorTextFieldDialog( + title: dialogTitle, + value: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + autoSelectAllText: true, + confirm: createView, + ).show(context); + } else { + createView(LocaleKeys.menuAppHeader_defaultNewPageName.tr()); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart index c7d026fe350d..5478cf910fcb 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart @@ -1,11 +1,11 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/menu/menu_bloc.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flutter/material.dart'; import 'package:flowy_infra_ui/style_widget/extension.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class SidebarNewPageButton extends StatelessWidget { @@ -20,7 +20,15 @@ class SidebarNewPageButton extends StatelessWidget { fillColor: Colors.transparent, hoverColor: Colors.transparent, fontColor: Theme.of(context).colorScheme.tertiary, - onPressed: () async => await _showCreatePageDialog(context), + onPressed: () async => await createViewAndShowRenameDialogIfNeeded( + context, + LocaleKeys.newPageText.tr(), + (viewName) { + if (viewName.isNotEmpty) { + context.read().add(MenuEvent.createApp(viewName)); + } + }, + ), heading: Container( width: 16, height: 16, @@ -44,16 +52,4 @@ class SidebarNewPageButton extends StatelessWidget { ), ); } - - Future _showCreatePageDialog(BuildContext context) async { - return NavigatorTextFieldDialog( - title: LocaleKeys.newPageText.tr(), - value: '', - confirm: (value) { - if (value.isNotEmpty) { - context.read().add(MenuEvent.createApp(value)); - } - }, - ).show(context); - } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart index 992c8f5105cd..ef1d157932ce 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart @@ -7,6 +7,7 @@ import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart'; @@ -350,22 +351,21 @@ class _SingleInnerViewItemState extends State { createNewView, ) { if (createNewView) { - NavigatorTextFieldDialog( - title: _convertLayoutToHintText(pluginBuilder.layoutType!), - value: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), - autoSelectAllText: true, - confirm: (value) { - if (value.isNotEmpty) { + createViewAndShowRenameDialogIfNeeded( + context, + _convertLayoutToHintText(pluginBuilder.layoutType!), + (viewName) { + if (viewName.isNotEmpty) { context.read().add( ViewEvent.createView( - value, + viewName, pluginBuilder.layoutType!, openAfterCreated: openAfterCreated, ), ); } }, - ).show(context); + ); } context.read().add( const ViewEvent.setIsExpanded(true), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/create_file_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/create_file_setting.dart new file mode 100644 index 000000000000..7f8882792834 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/create_file_setting.dart @@ -0,0 +1,37 @@ +import 'package:appflowy/workspace/application/settings/prelude.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/theme_setting_entry_template.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +bool _prevSetting = false; + +class CreateFileSettings extends StatelessWidget { + CreateFileSettings({ + super.key, + }); + + final cubit = CreateFileSettingsCubit(_prevSetting); + + @override + Widget build(BuildContext context) { + return ThemeSettingEntryTemplateWidget( + label: 'Show rename dialog when creating a new file', + trailing: [ + BlocProvider.value( + value: cubit, + child: BlocBuilder( + builder: (context, state) { + return Switch( + value: state, + onChanged: (value) { + cubit.toggle(value: value); + _prevSetting = value; + }, + ); + }, + ), + ), + ], + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart index 878ed9c70190..1c0c1faee9cc 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart @@ -1,4 +1,5 @@ import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/create_file_setting.dart'; import 'package:flowy_infra/plugins/bloc/dynamic_plugin_bloc.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -34,6 +35,7 @@ class SettingsAppearanceView extends StatelessWidget { TextDirectionSetting( currentTextDirection: state.textDirection, ), + CreateFileSettings(), ], ); }, diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart index 04bcca266842..74754fec2baa 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart @@ -1,9 +1,10 @@ -import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; +import 'dart:ui'; + import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/widget/dialog/dialog_size.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'dart:ui'; extension IntoDialog on Widget { Future show(BuildContext context) async { @@ -71,7 +72,7 @@ class StyledDialog extends StatelessWidget { maxWidth: maxWidth ?? double.infinity, ), child: ClipRRect( - borderRadius: borderRadius, + borderRadius: borderRadius ?? BorderRadius.zero, child: SingleChildScrollView( physics: StyledScrollPhysics(), //https://medium.com/saugo360/https-medium-com-saugo360-flutter-using-overlay-to-display-floating-widgets-2e6d0e8decb9 diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index b35303d219f7..b5c0cca55b0d 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -54,8 +54,8 @@ packages: dependency: "direct main" description: path: "." - ref: "6d68f90" - resolved-ref: "6d68f9003fa023d215dc5f20e8900f985c2cdaa1" + ref: a97c816 + resolved-ref: a97c816c1d8cfbc5644a8be49deae334c47261e3 url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git version: "1.3.0" @@ -1430,6 +1430,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + string_validator: + dependency: transitive + description: + name: string_validator + sha256: b419cf5d21d608522e6e7cafed4deb34b6f268c43df866e63c320bab98a08cf6 + url: "https://pub.dev" + source: hosted + version: "1.0.0" styled_widget: dependency: "direct main" description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index eb3c08fcaaa5..961687f3ee7e 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -47,7 +47,7 @@ dependencies: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: 6d68f90 + ref: a97c816 appflowy_popover: path: packages/appflowy_popover From 759cb27a4308f0c58b0764ccbffd1bc98747d7c9 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Thu, 14 Sep 2023 19:23:39 +0800 Subject: [PATCH 13/14] chore: update version to 0.3.2 (#3404) --- frontend/Makefile.toml | 2 +- frontend/appflowy_flutter/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml index 74b3af437622..1f08c693784c 100644 --- a/frontend/Makefile.toml +++ b/frontend/Makefile.toml @@ -25,7 +25,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true CARGO_MAKE_CRATE_FS_NAME = "dart_ffi" CARGO_MAKE_CRATE_NAME = "dart-ffi" LIB_NAME = "dart_ffi" -CURRENT_APP_VERSION = "0.3.1" +CURRENT_APP_VERSION = "0.3.2" FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite" PRODUCT_NAME = "AppFlowy" # CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 961687f3ee7e..e4645c287e3b 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.3.1 +version: 0.3.2 environment: sdk: ">=3.0.0 <4.0.0" From f6f80b48c946d66e43b0b1349ddc6653d5ca0d15 Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Thu, 14 Sep 2023 19:30:33 +0800 Subject: [PATCH 14/14] fix: some UI issues (#3403) --- .../presentation/calendar_event_card.dart | 23 +++++++++++++++++++ .../widgets/filter/create_filter_list.dart | 4 ++-- .../widgets/filter/filter_menu.dart | 1 - .../widgets/sort/create_sort_list.dart | 4 ++-- .../widgets/card/cells/card_cell.dart | 11 +++++++++ 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_card.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_card.dart index 45831d0ac33d..85f9ec36f9ce 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_card.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_card.dart @@ -167,6 +167,29 @@ class EventCard extends StatelessWidget { ); }); + renderHook.addTimestampCellHook((cellData, cardData, _) { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + flex: 3, + child: FlowyText.regular( + cellData.dateTime, + fontSize: 10, + color: Theme.of(context).hintColor, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ); + }); + renderHook.addSelectOptionHook((selectedOptions, cardData, _) { if (selectedOptions.isEmpty) { return const SizedBox.shrink(); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart index 8a92d573aca5..dee400002efa 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart @@ -114,7 +114,7 @@ class _GridCreateFilterListState extends State { class _FilterTextFieldDelegate extends SliverPersistentHeaderDelegate { _FilterTextFieldDelegate(); - double fixHeight = 46; + double fixHeight = 36; @override Widget build( @@ -123,7 +123,7 @@ class _FilterTextFieldDelegate extends SliverPersistentHeaderDelegate { bool overlapsContent, ) { return Container( - padding: const EdgeInsets.only(top: 4), + padding: const EdgeInsets.only(bottom: 4), height: fixHeight, child: FlowyTextField( hintText: LocaleKeys.grid_settings_filterBy.tr(), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/filter_menu.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/filter_menu.dart index ad0df4d48588..80b326abf404 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/filter_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/filter_menu.dart @@ -105,7 +105,6 @@ class _AddFilterButtonState extends State { return AppFlowyPopover( controller: popoverController, constraints: BoxConstraints.loose(const Size(200, 300)), - margin: const EdgeInsets.all(6), triggerActions: PopoverTriggerFlags.none, child: child, popupBuilder: (BuildContext context) { diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/sort/create_sort_list.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/sort/create_sort_list.dart index 440b2405e921..97238c6ed884 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/sort/create_sort_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/sort/create_sort_list.dart @@ -114,7 +114,7 @@ class _GridCreateSortListState extends State { class _SortTextFieldDelegate extends SliverPersistentHeaderDelegate { _SortTextFieldDelegate(); - double fixHeight = 46; + double fixHeight = 36; @override Widget build( @@ -123,7 +123,7 @@ class _SortTextFieldDelegate extends SliverPersistentHeaderDelegate { bool overlapsContent, ) { return Container( - padding: const EdgeInsets.only(top: 4), + padding: const EdgeInsets.only(bottom: 4), height: fixHeight, child: FlowyTextField( hintText: LocaleKeys.grid_settings_sortBy.tr(), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/card_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/card_cell.dart index db71945acd1b..71f57995f2a7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/card_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/card_cell.dart @@ -4,6 +4,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart' import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart'; import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/timestamp_entities.pb.dart'; import 'package:flutter/material.dart'; typedef CellRenderHook = Widget? Function( @@ -50,6 +51,16 @@ class RowCardRenderHook { renderHook[FieldType.DateTime] = _typeSafeHook(hook); } + /// Add a render hook for [FieldType.LastEditedTime] and [FieldType.CreatedTime] + void addTimestampCellHook( + CellRenderHook hook, + ) { + renderHook[FieldType.LastEditedTime] = + _typeSafeHook(hook); + renderHook[FieldType.CreatedTime] = + _typeSafeHook(hook); + } + CellRenderHook _typeSafeHook( CellRenderHook hook, ) {