From cd3be696dc2aee39b797e0e3fd4cca483b96b57a Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 13 Nov 2024 20:36:15 +0800 Subject: [PATCH] feat: optimize the workspace menu loading speed (#6758) * feat: optimize the workspace menu loading speed * chore: add workspace menu animation * feat: optimize workspace menu loading speed on mobile * test: workspace member count test * fix: rust ci --- .github/workflows/rust_ci.yaml | 5 +- .../collaborative_workspace_test.dart | 29 ++++- .../workspace_menu_bottom_sheet.dart | 5 +- .../workspace/_sidebar_workspace_actions.dart | 2 +- .../workspace/_sidebar_workspace_menu.dart | 100 ++++++++---------- .../sidebar/workspace/sidebar_workspace.dart | 5 +- .../user_manager/manager_user_workspace.rs | 2 + 7 files changed, 87 insertions(+), 61 deletions(-) diff --git a/.github/workflows/rust_ci.yaml b/.github/workflows/rust_ci.yaml index 2702cbd365..4f665a8655 100644 --- a/.github/workflows/rust_ci.yaml +++ b/.github/workflows/rust_ci.yaml @@ -43,6 +43,7 @@ jobs: cp deploy.env .env sed -i '' 's|RUST_LOG=.*|RUST_LOG=trace|' .env sed -i '' 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env + sed -i '' 's|APPFLOWY_AI_OPENAI_API_KEY=.*|APPFLOWY_AI_OPENAI_API_KEY=${{ secrets.CI_OPENAI_API_KEY }}|' .env - name: Ensure AppFlowy-Cloud is Running with Correct Version working-directory: AppFlowy-Cloud @@ -137,7 +138,7 @@ jobs: run: | cp deploy.env .env sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env - sed -i 's|GOTRUE_MAILER_AUTOCONFIRM=.*|GOTRUE_MAILER_AUTOCONFIRM=true|' .env + sed -i 's|GOTRUE_MAILER_AUTOCONFIRM=.*|GOTRUE_MAILER_AUTOCONFIRM=true|' .env sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env - name: Ensure AppFlowy-Cloud is Running with Correct Version @@ -160,7 +161,7 @@ jobs: else echo "No volumes to remove." fi - + docker compose pull docker compose up -d echo "Waiting for the container to be ready..." diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/collaborative_workspace_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/collaborative_workspace_test.dart index 53f5db6d07..accf9dfe0e 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/collaborative_workspace_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/collaborative_workspace_test.dart @@ -13,7 +13,7 @@ import '../../../shared/util.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - group('collaborative workspace: ', () { + group('collaborative workspace:', () { // combine the create and delete workspace test to reduce the time testWidgets('create a new workspace, open it and then delete it', (tester) async { @@ -74,5 +74,32 @@ void main() { }, ); }); + + testWidgets('check the member count immediately after creating a workspace', + (tester) async { + // only run the test when the feature flag is on + if (!FeatureFlag.collaborativeWorkspace.isOn) { + return; + } + + await tester.initializeAppFlowy( + cloudType: AuthenticatorType.appflowyCloudSelfHost, + ); + await tester.tapGoogleLoginInButton(); + await tester.expectToSeeHomePageWithGetStartedPage(); + + const name = 'AppFlowy.IO'; + // the workspace will be opened after created + await tester.createCollaborativeWorkspace(name); + + final loading = find.byType(Loading); + await tester.pumpUntilNotFound(loading); + + await tester.openCollaborativeWorkspaceMenu(); + + // expect to see the member count + final memberCount = find.text('1 member'); + expect(memberCount, findsNWidgets(2)); + }); }); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart index 59b381ce10..4dde3a94b6 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart @@ -234,6 +234,7 @@ class _WorkspaceMenuItemContent extends StatelessWidget { @override Widget build(BuildContext context) { + final memberCount = workspace.memberCount.toInt(); return Padding( padding: const EdgeInsets.only(left: 12), child: Column( @@ -247,10 +248,10 @@ class _WorkspaceMenuItemContent extends StatelessWidget { overflow: TextOverflow.ellipsis, ), FlowyText( - context.read().state.isLoading + memberCount == 0 ? '' : LocaleKeys.settings_appearance_members_membersCount.plural( - context.read().state.members.length, + memberCount, ), fontSize: 10.0, color: Theme.of(context).hintColor, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart index a74da7de46..a0be2b43b3 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart @@ -170,7 +170,7 @@ class _WorkspaceMoreActionWrapper extends CustomActionCell { switch (inner) { case WorkspaceMoreAction.delete: return FlowySvg( - FlowySvgs.delete_s, + FlowySvgs.trash_s, color: onHover ? Theme.of(context).colorScheme.error : null, ); case WorkspaceMoreAction.rename: diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart index bdb73876be..af8e0cb8c4 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart @@ -196,26 +196,23 @@ class _WorkspaceMenuItemState extends State { } Widget _buildRightIcon(BuildContext context, ValueNotifier isHovered) { - // only the owner can update or delete workspace. - if (context.read().state.isLoading) { - return const SizedBox.shrink(); - } - return Row( children: [ - ValueListenableBuilder( - valueListenable: isHovered, - builder: (context, value, child) { - return Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Opacity( - opacity: value ? 1.0 : 0.0, - child: child, - ), - ); - }, - child: WorkspaceMoreActionList(workspace: widget.workspace), - ), + // only the owner can update or delete workspace. + if (!context.read().state.isLoading) + ValueListenableBuilder( + valueListenable: isHovered, + builder: (context, value, child) { + return Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Opacity( + opacity: value ? 1.0 : 0.0, + child: child, + ), + ); + }, + child: WorkspaceMoreActionList(workspace: widget.workspace), + ), const HSpace(8.0), if (widget.isSelected) ...[ const Padding( @@ -244,43 +241,38 @@ class _WorkspaceInfo extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - final members = state.members; - return FlowyButton( - onTap: () => _openWorkspace(context), - iconPadding: 10.0, - leftIconSize: const Size.square(32), - leftIcon: const SizedBox.square(dimension: 32), - rightIcon: const HSpace(32.0), - text: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // workspace name - FlowyText.medium( - workspace.name, - fontSize: 14.0, - figmaLineHeight: 17.0, - overflow: TextOverflow.ellipsis, - withTooltip: true, - ), - // workspace members count - FlowyText.regular( - state.isLoading - ? '' - : LocaleKeys.settings_appearance_members_membersCount - .plural( - members.length, - ), - fontSize: 10.0, - figmaLineHeight: 12.0, - color: Theme.of(context).hintColor, - ), - ], + final memberCount = workspace.memberCount.toInt(); + return FlowyButton( + onTap: () => _openWorkspace(context), + iconPadding: 10.0, + leftIconSize: const Size.square(32), + leftIcon: const SizedBox.square(dimension: 32), + rightIcon: const HSpace(32.0), + text: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // workspace name + FlowyText.medium( + workspace.name, + fontSize: 14.0, + figmaLineHeight: 17.0, + overflow: TextOverflow.ellipsis, + withTooltip: true, ), - ); - }, + // workspace members count + FlowyText.regular( + memberCount == 0 + ? '' + : LocaleKeys.settings_appearance_members_membersCount.plural( + memberCount, + ), + fontSize: 10.0, + figmaLineHeight: 12.0, + color: Theme.of(context).hintColor, + ), + ], + ), ); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart index 8012fcc9e4..c3480a94bc 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart @@ -204,9 +204,12 @@ class _SidebarSwitchWorkspaceButtonState @override Widget build(BuildContext context) { return AppFlowyPopover( - direction: PopoverDirection.bottomWithLeftAligned, + direction: PopoverDirection.bottomWithCenterAligned, offset: const Offset(0, 5), constraints: const BoxConstraints(maxWidth: 300, maxHeight: 600), + animationDuration: Durations.short3, + beginScaleFactor: 1.0, + beginOpacity: 0.8, controller: _popoverController, triggerActions: PopoverTriggerFlags.none, onOpen: () { diff --git a/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs b/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs index 7a779ecabd..a51fea59c1 100644 --- a/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs +++ b/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs @@ -679,6 +679,7 @@ pub fn save_user_workspace( user_workspace_table::created_at.eq(&user_workspace.created_at), user_workspace_table::database_storage_id.eq(&user_workspace.database_storage_id), user_workspace_table::icon.eq(&user_workspace.icon), + user_workspace_table::member_count.eq(&user_workspace.member_count), )) .execute(conn)?; @@ -729,6 +730,7 @@ pub fn save_all_user_workspaces( user_workspace_table::created_at.eq(&user_workspace.created_at), user_workspace_table::database_storage_id.eq(&user_workspace.database_storage_id), user_workspace_table::icon.eq(&user_workspace.icon), + user_workspace_table::member_count.eq(&user_workspace.member_count), )) .execute(conn)?;