From 473eb8bcbe36ddb1806fe6a498db2bcd152f2d9f Mon Sep 17 00:00:00 2001 From: GitStart <1501599+gitstart@users.noreply.github.com> Date: Wed, 3 May 2023 10:22:16 +0300 Subject: [PATCH] feat: Only show the change cover button and delete button when it's hovering. (#2145) * feat: Only show the change cover button and delete button when it's hovering. * feat: Only show the change cover button and delete button when it's hovering --------- Co-authored-by: gitstart --- .../assets/test/workspaces/cover_image.zip | Bin 0 -> 8042 bytes .../integration_test/cover_image_test.dart | 54 ++++++++++ .../integration_test/util/data.dart | 3 +- .../plugins/cover/cover_node_widget.dart | 97 +++++++++++------- 4 files changed, 118 insertions(+), 36 deletions(-) create mode 100644 frontend/appflowy_flutter/assets/test/workspaces/cover_image.zip create mode 100644 frontend/appflowy_flutter/integration_test/cover_image_test.dart diff --git a/frontend/appflowy_flutter/assets/test/workspaces/cover_image.zip b/frontend/appflowy_flutter/assets/test/workspaces/cover_image.zip new file mode 100644 index 0000000000000000000000000000000000000000..65501f52fb8fc870868e5b5c04a99e4f858e2faa GIT binary patch literal 8042 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 literal 0 HcmV?d00001 diff --git a/frontend/appflowy_flutter/integration_test/cover_image_test.dart b/frontend/appflowy_flutter/integration_test/cover_image_test.dart new file mode 100644 index 0000000000..0bcc36f912 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/cover_image_test.dart @@ -0,0 +1,54 @@ +import 'package:flowy_infra_ui/widget/rounded_button.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'util/util.dart'; + +/// Integration tests for an empty board. The [TestWorkspaceService] will load +/// a workspace from an empty board `assets/test/workspaces/board.zip` for all +/// tests. +/// +/// To create another integration test with a preconfigured workspace. +/// Use the following steps. +/// 1. Create a new workspace from the AppFlowy launch screen. +/// 2. Modify the workspace until it is suitable as the starting point for +/// the integration test you need to land. +/// 3. Use a zip utility program to zip the workspace folder that you created. +/// 4. Add the zip file under `assets/test/workspaces/` +/// 5. Add a new enumeration to [TestWorkspace] in `integration_test/utils/data.dart`. +/// For example, if you added a workspace called `empty_calendar.zip`, +/// then [TestWorkspace] should have the following value: +/// ```dart +/// enum TestWorkspace { +/// board('board'), +/// empty_calendar('empty_calendar'); +/// +/// /* code */ +/// } +/// ``` +/// 6. Double check that the .zip file that you added is included as an asset in +/// the pubspec.yaml file under appflowy_flutter. +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + const service = TestWorkspaceService(TestWorkspace.coverImage); + + group('cover image', () { + setUpAll(() async => await service.setUpAll()); + setUp(() async => await service.setUp()); + + testWidgets( + 'hovering on cover image will display change and delete cover image buttons', + (tester) async { + await tester.initializeAppFlowy(); + expect(find.byType(Image), findsOneWidget); + + final TestPointer pointer = TestPointer(1, PointerDeviceKind.mouse); + final imageFinder = find.byType(Image); + Offset offset = tester.getCenter(imageFinder); + + pointer.hover(offset); + expect(find.byType(RoundedTextButton), findsOneWidget); + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/util/data.dart b/frontend/appflowy_flutter/integration_test/util/data.dart index 4cbb1a34f2..c6912ddeca 100644 --- a/frontend/appflowy_flutter/integration_test/util/data.dart +++ b/frontend/appflowy_flutter/integration_test/util/data.dart @@ -9,7 +9,8 @@ import 'package:shared_preferences/shared_preferences.dart'; enum TestWorkspace { board("board"), - emptyDocument("empty_document"); + emptyDocument("empty_document"), + coverImage("cover_image"); const TestWorkspace(this._name); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/cover_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/cover_node_widget.dart index 9d3629a400..4942022bba 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/cover_node_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/cover_node_widget.dart @@ -393,21 +393,32 @@ class _CoverImageState extends State<_CoverImage> { mainAxisSize: MainAxisSize.min, children: [ AppFlowyPopover( + onClose: () { + setOverlayButtonsHidden(true); + }, offset: const Offset(-125, 10), controller: popoverController, direction: PopoverDirection.bottomWithCenterAligned, constraints: BoxConstraints.loose(const Size(380, 450)), margin: EdgeInsets.zero, - child: RoundedTextButton( - onPressed: () { - popoverController.show(); - }, - hoverColor: Theme.of(context).colorScheme.surface, - textColor: Theme.of(context).colorScheme.tertiary, - fillColor: Theme.of(context).colorScheme.surface.withOpacity(0.8), - width: 120, - height: 28, - title: LocaleKeys.document_plugins_cover_changeCover.tr(), + child: Visibility( + maintainState: true, + maintainAnimation: true, + maintainSize: true, + visible: !isOverlayButtonsHidden, + child: RoundedTextButton( + onPressed: () { + popoverController.show(); + setOverlayButtonsHidden(true); + }, + hoverColor: Theme.of(context).colorScheme.surface, + textColor: Theme.of(context).colorScheme.onSurface, + fillColor: + Theme.of(context).colorScheme.surface.withOpacity(0.8), + width: 120, + height: 28, + title: LocaleKeys.document_plugins_cover_changeCover.tr(), + ), ), popupBuilder: (BuildContext popoverContext) { return ChangeCoverPopover( @@ -418,18 +429,24 @@ class _CoverImageState extends State<_CoverImage> { }, ), const SizedBox(width: 10), - FlowyIconButton( - fillColor: Theme.of(context).colorScheme.surface.withOpacity(0.8), - hoverColor: Theme.of(context).colorScheme.surface, - iconPadding: const EdgeInsets.all(5), - width: 28, - icon: svgWidget( - 'editor/delete', - color: Theme.of(context).colorScheme.tertiary, + Visibility( + maintainAnimation: true, + maintainSize: true, + maintainState: true, + visible: !isOverlayButtonsHidden, + child: FlowyIconButton( + fillColor: Theme.of(context).colorScheme.surface.withOpacity(0.8), + hoverColor: Theme.of(context).colorScheme.surface, + iconPadding: const EdgeInsets.all(5), + width: 28, + icon: svgWidget( + 'editor/delete', + color: Theme.of(context).colorScheme.onSurface, + ), + onPressed: () { + widget.onCoverChanged(CoverSelectionType.initial, null); + }, ), - onPressed: () { - widget.onCoverChanged(CoverSelectionType.initial, null); - }, ), ], ), @@ -477,20 +494,30 @@ class _CoverImageState extends State<_CoverImage> { break; } //OverflowBox needs to be wraped by a widget with constraints(or from its parent) first,otherwise it will occur an error - return SizedBox( - height: height, - child: OverflowBox( - maxWidth: screenSize.width, - child: Stack( - children: [ - Container( - padding: const EdgeInsets.only(bottom: 10), - height: double.infinity, - width: double.infinity, - child: coverImage, - ), - hasCover ? _buildCoverOverlayButtons(context) : const SizedBox() - ], + return MouseRegion( + onEnter: (event) { + setOverlayButtonsHidden(false); + }, + onExit: (event) { + setOverlayButtonsHidden(true); + }, + child: SizedBox( + height: height, + child: OverflowBox( + maxWidth: screenSize.width, + child: Stack( + children: [ + Container( + padding: const EdgeInsets.only(bottom: 10), + height: double.infinity, + width: double.infinity, + child: coverImage, + ), + hasCover + ? _buildCoverOverlayButtons(context) + : const SizedBox.shrink() + ], + ), ), ), );