diff --git a/.github/workflows/flutter_ci.yaml b/.github/workflows/flutter_ci.yaml index ebb5f2c5a2..75181cdf94 100644 --- a/.github/workflows/flutter_ci.yaml +++ b/.github/workflows/flutter_ci.yaml @@ -134,6 +134,7 @@ jobs: fail_ci_if_error: true verbose: true os: ${{ matrix.os }} + token: ${{ secrets.CODECOV_TOKEN }} attempt_limit: 20 attempt_delay: 10000 diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 3ca838631e..974406e8d0 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -136,5 +136,6 @@ jobs: fail_ci_if_error: true verbose: true os: ${{ matrix.os }} + token: ${{ secrets.CODECOV_TOKEN }} attempt_limit: 20 attempt_delay: 10000 \ No newline at end of file diff --git a/frontend/appflowy_flutter/integration_test/document/document_with_database_test.dart b/frontend/appflowy_flutter/integration_test/document/document_with_database_test.dart index 46a3dcd296..6a044ebf8e 100644 --- a/frontend/appflowy_flutter/integration_test/document/document_with_database_test.dart +++ b/frontend/appflowy_flutter/integration_test/document/document_with_database_test.dart @@ -1,9 +1,12 @@ +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'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart'; +import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/uuid.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -61,6 +64,54 @@ void main() { findsOneWidget, ); }); + + testWidgets('create a grid inside a document', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapGoButton(); + + await createInlineDatabase(tester, ViewLayoutPB.Grid); + + // validate the referenced grid is inserted + expect( + find.descendant( + of: find.byType(AppFlowyEditor), + matching: find.byType(GridPage), + ), + findsOneWidget, + ); + }); + + testWidgets('create a board inside a document', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapGoButton(); + + await createInlineDatabase(tester, ViewLayoutPB.Board); + + // validate the referenced grid is inserted + expect( + find.descendant( + of: find.byType(AppFlowyEditor), + matching: find.byType(BoardPage), + ), + findsOneWidget, + ); + }); + + testWidgets('create a calendar inside a document', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapGoButton(); + + await createInlineDatabase(tester, ViewLayoutPB.Calendar); + + // validate the referenced grid is inserted + expect( + find.descendant( + of: find.byType(AppFlowyEditor), + matching: find.byType(CalendarPage), + ), + findsOneWidget, + ); + }); }); } @@ -75,11 +126,13 @@ Future insertReferenceDatabase( await tester.createNewPageWithName( name: name, layout: layout, + openAfterCreated: false, ); // create a new document await tester.createNewPageWithName( name: 'insert_a_reference_${layout.name}', layout: ViewLayoutPB.Document, + openAfterCreated: true, ); // tap the first line of the document await tester.editor.tapLineOfEditorAt(0); @@ -98,3 +151,38 @@ Future insertReferenceDatabase( expect(referencedDatabase, findsOneWidget); await tester.tapButton(referencedDatabase); } + +Future createInlineDatabase( + WidgetTester tester, + ViewLayoutPB layout, +) async { + // create a new document + final documentName = 'insert_a_inline_${layout.name}'; + await tester.createNewPageWithName( + name: documentName, + layout: ViewLayoutPB.Document, + openAfterCreated: true, + ); + // tap the first line of the document + await tester.editor.tapLineOfEditorAt(0); + // insert a referenced view + await tester.editor.showSlashMenu(); + final name = switch (layout) { + ViewLayoutPB.Grid => LocaleKeys.document_slashMenu_grid_createANewGrid.tr(), + ViewLayoutPB.Board => + LocaleKeys.document_slashMenu_board_createANewBoard.tr(), + ViewLayoutPB.Calendar => + LocaleKeys.document_slashMenu_calendar_createANewCalendar.tr(), + _ => '', + }; + await tester.editor.tapSlashMenuItemWithName( + name, + ); + await tester.pumpAndSettle(); + + final childViews = tester + .widget(tester.findPageName(documentName)) + .view + .childViews; + expect(childViews.length, 1); +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart index 9a5f831633..16c9608a27 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -234,13 +234,19 @@ class _AppFlowyEditorPageState extends State { ), ), DatabaseBlockKeys.gridType: DatabaseViewBlockComponentBuilder( - configuration: configuration, + configuration: configuration.copyWith( + padding: (_) => const EdgeInsets.symmetric(vertical: 10), + ), ), DatabaseBlockKeys.boardType: DatabaseViewBlockComponentBuilder( - configuration: configuration, + configuration: configuration.copyWith( + padding: (_) => const EdgeInsets.symmetric(vertical: 10), + ), ), DatabaseBlockKeys.calendarType: DatabaseViewBlockComponentBuilder( - configuration: configuration, + configuration: configuration.copyWith( + padding: (_) => const EdgeInsets.symmetric(vertical: 10), + ), ), CalloutBlockKeys.type: CalloutBlockComponentBuilder( configuration: configuration, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart index 381476ccb8..99c4479ea1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart @@ -82,23 +82,16 @@ class _LinkToPageMenuState extends State { final _focusNode = FocusNode(debugLabel: 'reference_list_widget'); EditorStyle get style => widget.editorState.editorStyle; int _selectedIndex = 0; - int _totalItems = 0; - Future)>>? _availableLayout; - final Map _items = {}; + final int _totalItems = 0; + Future>? _availableLayout; + final List _items = []; - Future)>> fetchItems() async { + Future> fetchItems() async { final items = await ViewBackendService().fetchViewsWithLayoutType(widget.layoutType); - - int index = 0; - for (final (app, children) in items) { - for (final view in children) { - _items.putIfAbsent(index, () => (app, view)); - index += 1; - } - } - - _totalItems = _items.length; + _items + ..clear() + ..addAll(items); return items; } @@ -176,8 +169,8 @@ class _LinkToPageMenuState extends State { newSelectedIndex %= _totalItems; } else if (event.logicalKey == LogicalKeyboardKey.enter) { widget.onSelected( - _items[_selectedIndex]!.$1, - _items[_selectedIndex]!.$2, + _items[_selectedIndex], + _items[_selectedIndex], ); } @@ -191,10 +184,10 @@ class _LinkToPageMenuState extends State { Widget _buildListWidget( BuildContext context, int selectedIndex, - Future)>>? items, + Future>? items, ) { int index = 0; - return FutureBuilder)>>( + return FutureBuilder>( builder: (context, snapshot) { if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) { @@ -211,35 +204,23 @@ class _LinkToPageMenuState extends State { ]; if (views != null && views.isNotEmpty) { - for (final (view, viewChildren) in views) { - if (viewChildren.isNotEmpty) { - children.add( - Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: FlowyText.regular( - view.name, - ), + for (final view in views) { + children.add( + FlowyButton( + isSelected: index == _selectedIndex, + leftIcon: svgWidget( + view.iconName, + color: Theme.of(context).iconTheme.color, ), - ); + text: FlowyText.regular(view.name), + onTap: () => widget.onSelected(view, view), + ), + ); - for (final value in viewChildren) { - children.add( - FlowyButton( - isSelected: index == _selectedIndex, - leftIcon: svgWidget( - value.iconName, - color: Theme.of(context).iconTheme.color, - ), - text: FlowyText.regular(value.name), - onTap: () => widget.onSelected(view, value), - ), - ); - - index += 1; - } - } + index += 1; } } + return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: children, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart index c9242dc4e7..97532363eb 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart @@ -82,6 +82,11 @@ class _DatabaseBlockComponentWidgetState }, ); + child = Padding( + padding: padding, + child: child, + ); + if (widget.actionBuilder != null) { child = BlockComponentActionWrapper( node: widget.node, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/inline_database_menu_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/inline_database_menu_item.dart index c07b0b47b3..0576d6e4b4 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/inline_database_menu_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/inline_database_menu_item.dart @@ -17,11 +17,8 @@ SelectionMenuItem inlineGridMenuItem(DocumentBloc documentBloc) => ), keywords: ['grid', 'database'], handler: (editorState, menuService, context) async { - if (!documentBloc.view.hasParentViewId()) { - return; - } - - final parentViewId = documentBloc.view.parentViewId; + // create the view inside current page + final parentViewId = documentBloc.view.id; ViewBackendService.createView( parentViewId: parentViewId, openAfterCreate: false, @@ -45,11 +42,8 @@ SelectionMenuItem inlineBoardMenuItem(DocumentBloc documentBloc) => ), keywords: ['board', 'kanban', 'database'], handler: (editorState, menuService, context) async { - if (!documentBloc.view.hasParentViewId()) { - return; - } - - final parentViewId = documentBloc.view.parentViewId; + // create the view inside current page + final parentViewId = documentBloc.view.id; ViewBackendService.createView( parentViewId: parentViewId, name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), @@ -72,11 +66,8 @@ SelectionMenuItem inlineCalendarMenuItem(DocumentBloc documentBloc) => ), keywords: ['calendar', 'database'], handler: (editorState, menuService, context) async { - if (!documentBloc.view.hasParentViewId()) { - return; - } - - final parentViewId = documentBloc.view.parentViewId; + // create the view inside current page + final parentViewId = documentBloc.view.id; ViewBackendService.createView( parentViewId: parentViewId, name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart index acbc472e86..65f598bc50 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart @@ -2,7 +2,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selec import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_service.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; enum MentionType { @@ -98,17 +97,11 @@ class InlinePageReferenceService { Future> generatePageItems(String character) async { final service = ViewBackendService(); - final List<(ViewPB, List)> pbViews = await service.fetchViews( - (_, __) => true, - ); - if (pbViews.isEmpty) { + final views = await service.fetchViews(); + if (views.isEmpty) { return []; } final List pages = []; - final List views = []; - for (final element in pbViews) { - views.addAll(element.$2); - } views.sort(((a, b) => b.createTime.compareTo(a.createTime))); for (final view in views) { diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart index 295b9453e7..12983016d2 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart @@ -173,41 +173,51 @@ class ViewBackendService { return FolderEventMoveNestedView(payload).send(); } - Future)>> fetchViewsWithLayoutType( + Future> fetchViewsWithLayoutType( ViewLayoutPB? layoutType, ) async { - return fetchViews((workspace, view) { - if (layoutType != null) { - return view.layout == layoutType; - } - return true; - }); + final views = await fetchViews(); + if (layoutType == null) { + return views; + } + return views + .where( + (element) => layoutType == element.layout, + ) + .toList(); } - Future)>> fetchViews( - bool Function(WorkspaceSettingPB workspace, ViewPB view) filter, - ) async { - final result = <(ViewPB, List)>[]; + Future> fetchViews() async { + final result = []; return FolderEventGetCurrentWorkspace().send().then((value) async { final workspaces = value.getLeftOrNull(); if (workspaces != null) { final views = workspaces.workspace.views; for (final view in views) { - final childViews = await getChildViews(viewId: view.id).then( - (value) => value - .getLeftOrNull>() - ?.where((e) => filter(workspaces, e)) - .toList(), - ); - if (childViews != null && childViews.isNotEmpty) { - result.add((view, childViews)); - } + result.add(view); + final childViews = await getAllViews(view); + result.addAll(childViews); } } return result; }); } + Future> getAllViews(ViewPB view) async { + final result = []; + final childViews = await getChildViews(viewId: view.id).then( + (value) => value.getLeftOrNull>()?.toList(), + ); + if (childViews != null && childViews.isNotEmpty) { + result.addAll(childViews); + final views = await Future.wait( + childViews.map((e) async => await getAllViews(e)), + ); + result.addAll(views.expand((element) => element)); + } + return result; + } + static Future> getView( String viewID, ) async {