mirror of
				https://github.com/AppFlowy-IO/AppFlowy.git
				synced 2025-11-03 19:43:52 +00:00 
			
		
		
		
	fix: ingore keyup event in cover title (#6468)
* fix: ingore keyup event in cover title * feat: add text field with line metric * chore: refactor test strcuture * test: add arrow down key test
This commit is contained in:
		
							parent
							
								
									caa882dc37
								
							
						
					
					
						commit
						153416604d
					
				@ -1,28 +1,18 @@
 | 
				
			|||||||
import 'document/document_delete_block_test.dart' as document_delete_block_test;
 | 
					import 'document/document_test_runner.dart' as document_test_runner;
 | 
				
			||||||
import 'document/document_drag_block_test.dart' as document_drag_block_test;
 | 
					 | 
				
			||||||
import 'document/document_option_actions_test.dart'
 | 
					 | 
				
			||||||
    as document_option_actions_test;
 | 
					 | 
				
			||||||
import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test;
 | 
					import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test;
 | 
				
			||||||
import 'uncategorized/anon_user_continue_test.dart' as anon_user_continue_test;
 | 
					import 'uncategorized/uncategorized_test_runner.dart'
 | 
				
			||||||
import 'uncategorized/appflowy_cloud_auth_test.dart'
 | 
					    as uncategorized_test_runner;
 | 
				
			||||||
    as appflowy_cloud_auth_test;
 | 
					 | 
				
			||||||
import 'uncategorized/empty_test.dart' as preset_af_cloud_env_test;
 | 
					 | 
				
			||||||
import 'uncategorized/user_setting_sync_test.dart' as user_sync_test;
 | 
					 | 
				
			||||||
import 'workspace/workspace_test_runner.dart' as workspace_test_runner;
 | 
					import 'workspace/workspace_test_runner.dart' as workspace_test_runner;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Future<void> main() async {
 | 
					Future<void> main() async {
 | 
				
			||||||
  preset_af_cloud_env_test.main();
 | 
					  // uncategorized
 | 
				
			||||||
  appflowy_cloud_auth_test.main();
 | 
					  uncategorized_test_runner.main();
 | 
				
			||||||
  user_sync_test.main();
 | 
					 | 
				
			||||||
  anon_user_continue_test.main();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // workspace
 | 
					  // workspace
 | 
				
			||||||
  workspace_test_runner.startTesting();
 | 
					  workspace_test_runner.main();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // document
 | 
					  // document
 | 
				
			||||||
  document_option_actions_test.main();
 | 
					  document_test_runner.main();
 | 
				
			||||||
  document_drag_block_test.main();
 | 
					 | 
				
			||||||
  document_delete_block_test.main();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // sidebar
 | 
					  // sidebar
 | 
				
			||||||
  sidebar_move_page_test.main();
 | 
					  sidebar_move_page_test.main();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,60 +0,0 @@
 | 
				
			|||||||
import 'package:appflowy/env/cloud_env.dart';
 | 
					 | 
				
			||||||
import 'package:appflowy/generated/locale_keys.g.dart';
 | 
					 | 
				
			||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart';
 | 
					 | 
				
			||||||
import 'package:appflowy_editor/appflowy_editor.dart';
 | 
					 | 
				
			||||||
import 'package:easy_localization/easy_localization.dart';
 | 
					 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					 | 
				
			||||||
import 'package:flutter_test/flutter_test.dart';
 | 
					 | 
				
			||||||
import 'package:integration_test/integration_test.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import '../../../shared/constants.dart';
 | 
					 | 
				
			||||||
import '../../../shared/util.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void main() {
 | 
					 | 
				
			||||||
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  group('document delete block: ', () {
 | 
					 | 
				
			||||||
    testWidgets('hover on the block and delete it', (tester) async {
 | 
					 | 
				
			||||||
      await tester.initializeAppFlowy(
 | 
					 | 
				
			||||||
        cloudType: AuthenticatorType.appflowyCloudSelfHost,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      await tester.tapGoogleLoginInButton();
 | 
					 | 
				
			||||||
      await tester.expectToSeeHomePageWithGetStartedPage();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // open getting started page
 | 
					 | 
				
			||||||
      await tester.openPage(Constants.gettingStartedPageName);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // before delete
 | 
					 | 
				
			||||||
      final path = [1];
 | 
					 | 
				
			||||||
      final beforeDeletedBlock = tester.editor.getNodeAtPath(path);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // hover on the block and delete it
 | 
					 | 
				
			||||||
      final optionButton = find.byWidgetPredicate(
 | 
					 | 
				
			||||||
        (widget) =>
 | 
					 | 
				
			||||||
            widget is DraggableOptionButton &&
 | 
					 | 
				
			||||||
            widget.blockComponentContext.node.path.equals(path),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await tester.hoverOnWidget(
 | 
					 | 
				
			||||||
        optionButton,
 | 
					 | 
				
			||||||
        onHover: () async {
 | 
					 | 
				
			||||||
          // click the delete button
 | 
					 | 
				
			||||||
          await tester.tapButton(optionButton);
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      await tester.pumpAndSettle(Durations.short1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // click the delete button
 | 
					 | 
				
			||||||
      final deleteButton =
 | 
					 | 
				
			||||||
          find.findTextInFlowyText(LocaleKeys.button_delete.tr());
 | 
					 | 
				
			||||||
      await tester.tapButton(deleteButton);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // wait for the deletion
 | 
					 | 
				
			||||||
      await tester.pumpAndSettle(Durations.short1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // check if the block is deleted
 | 
					 | 
				
			||||||
      final afterDeletedBlock = tester.editor.getNodeAtPath([1]);
 | 
					 | 
				
			||||||
      expect(afterDeletedBlock.id, isNot(equals(beforeDeletedBlock.id)));
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,101 +0,0 @@
 | 
				
			|||||||
import 'package:appflowy/env/cloud_env.dart';
 | 
					 | 
				
			||||||
import 'package:appflowy/generated/locale_keys.g.dart';
 | 
					 | 
				
			||||||
import 'package:easy_localization/easy_localization.dart';
 | 
					 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					 | 
				
			||||||
import 'package:flutter_test/flutter_test.dart';
 | 
					 | 
				
			||||||
import 'package:integration_test/integration_test.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import '../../../shared/constants.dart';
 | 
					 | 
				
			||||||
import '../../../shared/util.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void main() {
 | 
					 | 
				
			||||||
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  group('document option actions:', () {
 | 
					 | 
				
			||||||
    testWidgets('drag block to the top', (tester) async {
 | 
					 | 
				
			||||||
      await tester.initializeAppFlowy(
 | 
					 | 
				
			||||||
        cloudType: AuthenticatorType.appflowyCloudSelfHost,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      await tester.tapGoogleLoginInButton();
 | 
					 | 
				
			||||||
      await tester.expectToSeeHomePageWithGetStartedPage();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // open getting started page
 | 
					 | 
				
			||||||
      await tester.openPage(Constants.gettingStartedPageName);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // before move
 | 
					 | 
				
			||||||
      final beforeMoveBlock = tester.editor.getNodeAtPath([1]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // move the desktop guide to the top, above the getting started
 | 
					 | 
				
			||||||
      await tester.editor.dragBlock(
 | 
					 | 
				
			||||||
        [1],
 | 
					 | 
				
			||||||
        const Offset(20, -80),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // wait for the move animation to complete
 | 
					 | 
				
			||||||
      await tester.pumpAndSettle(Durations.short1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // check if the block is moved to the top
 | 
					 | 
				
			||||||
      final afterMoveBlock = tester.editor.getNodeAtPath([0]);
 | 
					 | 
				
			||||||
      expect(afterMoveBlock.delta, beforeMoveBlock.delta);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    testWidgets('drag block to other block\'s child', (tester) async {
 | 
					 | 
				
			||||||
      await tester.initializeAppFlowy(
 | 
					 | 
				
			||||||
        cloudType: AuthenticatorType.appflowyCloudSelfHost,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      await tester.tapGoogleLoginInButton();
 | 
					 | 
				
			||||||
      await tester.expectToSeeHomePageWithGetStartedPage();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // open getting started page
 | 
					 | 
				
			||||||
      await tester.openPage(Constants.gettingStartedPageName);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // before move
 | 
					 | 
				
			||||||
      final beforeMoveBlock = tester.editor.getNodeAtPath([10]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // move the checkbox to the child of the block at path [9]
 | 
					 | 
				
			||||||
      await tester.editor.dragBlock(
 | 
					 | 
				
			||||||
        [10],
 | 
					 | 
				
			||||||
        const Offset(80, -30),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // wait for the move animation to complete
 | 
					 | 
				
			||||||
      await tester.pumpAndSettle(Durations.short1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // check if the block is moved to the child of the block at path [9]
 | 
					 | 
				
			||||||
      final afterMoveBlock = tester.editor.getNodeAtPath([9, 0]);
 | 
					 | 
				
			||||||
      expect(afterMoveBlock.delta, beforeMoveBlock.delta);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    testWidgets('copy block link', (tester) async {
 | 
					 | 
				
			||||||
      await tester.initializeAppFlowy(
 | 
					 | 
				
			||||||
        cloudType: AuthenticatorType.appflowyCloudSelfHost,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      await tester.tapGoogleLoginInButton();
 | 
					 | 
				
			||||||
      await tester.expectToSeeHomePageWithGetStartedPage();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // open getting started page
 | 
					 | 
				
			||||||
      await tester.openPage(Constants.gettingStartedPageName);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // hover and click on the option menu button beside the block component.
 | 
					 | 
				
			||||||
      await tester.editor.hoverAndClickOptionMenuButton([0]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // click the copy link to block option
 | 
					 | 
				
			||||||
      await tester.tap(
 | 
					 | 
				
			||||||
        find.findTextInFlowyText(
 | 
					 | 
				
			||||||
          LocaleKeys.document_plugins_optionAction_copyLinkToBlock.tr(),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      await tester.pumpAndSettle(Durations.short1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // check the clipboard
 | 
					 | 
				
			||||||
      final content = await Clipboard.getData(Clipboard.kTextPlain);
 | 
					 | 
				
			||||||
      expect(
 | 
					 | 
				
			||||||
        content?.text,
 | 
					 | 
				
			||||||
        matches(
 | 
					 | 
				
			||||||
          r'^https:\/\/appflowy\.com\/app\/[a-f0-9-]{36}\/[a-f0-9-]{36}\?blockId=[A-Za-z0-9_-]+$',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,5 +1,7 @@
 | 
				
			|||||||
import 'package:appflowy/env/cloud_env.dart';
 | 
					import 'package:appflowy/env/cloud_env.dart';
 | 
				
			||||||
import 'package:appflowy/generated/locale_keys.g.dart';
 | 
					import 'package:appflowy/generated/locale_keys.g.dart';
 | 
				
			||||||
 | 
					import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart';
 | 
				
			||||||
 | 
					import 'package:appflowy_editor/appflowy_editor.dart';
 | 
				
			||||||
import 'package:easy_localization/easy_localization.dart';
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
@ -97,5 +99,48 @@ void main() {
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWidgets('hover on the block and delete it', (tester) async {
 | 
				
			||||||
 | 
					      await tester.initializeAppFlowy(
 | 
				
			||||||
 | 
					        cloudType: AuthenticatorType.appflowyCloudSelfHost,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      await tester.tapGoogleLoginInButton();
 | 
				
			||||||
 | 
					      await tester.expectToSeeHomePageWithGetStartedPage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // open getting started page
 | 
				
			||||||
 | 
					      await tester.openPage(Constants.gettingStartedPageName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // before delete
 | 
				
			||||||
 | 
					      final path = [1];
 | 
				
			||||||
 | 
					      final beforeDeletedBlock = tester.editor.getNodeAtPath(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // hover on the block and delete it
 | 
				
			||||||
 | 
					      final optionButton = find.byWidgetPredicate(
 | 
				
			||||||
 | 
					        (widget) =>
 | 
				
			||||||
 | 
					            widget is DraggableOptionButton &&
 | 
				
			||||||
 | 
					            widget.blockComponentContext.node.path.equals(path),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await tester.hoverOnWidget(
 | 
				
			||||||
 | 
					        optionButton,
 | 
				
			||||||
 | 
					        onHover: () async {
 | 
				
			||||||
 | 
					          // click the delete button
 | 
				
			||||||
 | 
					          await tester.tapButton(optionButton);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      await tester.pumpAndSettle(Durations.short1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // click the delete button
 | 
				
			||||||
 | 
					      final deleteButton =
 | 
				
			||||||
 | 
					          find.findTextInFlowyText(LocaleKeys.button_delete.tr());
 | 
				
			||||||
 | 
					      await tester.tapButton(deleteButton);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // wait for the deletion
 | 
				
			||||||
 | 
					      await tester.pumpAndSettle(Durations.short1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // check if the block is deleted
 | 
				
			||||||
 | 
					      final afterDeletedBlock = tester.editor.getNodeAtPath([1]);
 | 
				
			||||||
 | 
					      expect(afterDeletedBlock.id, isNot(equals(beforeDeletedBlock.id)));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import 'package:integration_test/integration_test.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'document_option_actions_test.dart' as document_option_actions_test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void main() {
 | 
				
			||||||
 | 
					  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  document_option_actions_test.main();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import 'anon_user_continue_test.dart' as anon_user_continue_test;
 | 
				
			||||||
 | 
					import 'appflowy_cloud_auth_test.dart' as appflowy_cloud_auth_test;
 | 
				
			||||||
 | 
					import 'empty_test.dart' as preset_af_cloud_env_test;
 | 
				
			||||||
 | 
					import 'user_setting_sync_test.dart' as user_sync_test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void main() async {
 | 
				
			||||||
 | 
					  preset_af_cloud_env_test.main();
 | 
				
			||||||
 | 
					  appflowy_cloud_auth_test.main();
 | 
				
			||||||
 | 
					  user_sync_test.main();
 | 
				
			||||||
 | 
					  anon_user_continue_test.main();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -5,7 +5,7 @@ import 'collaborative_workspace_test.dart' as collaborative_workspace_test;
 | 
				
			|||||||
import 'share_menu_test.dart' as share_menu_test;
 | 
					import 'share_menu_test.dart' as share_menu_test;
 | 
				
			||||||
import 'workspace_settings_test.dart' as workspace_settings_test;
 | 
					import 'workspace_settings_test.dart' as workspace_settings_test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void startTesting() {
 | 
					void main() {
 | 
				
			||||||
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
 | 
					  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  workspace_settings_test.main();
 | 
					  workspace_settings_test.main();
 | 
				
			||||||
 | 
				
			|||||||
@ -220,5 +220,33 @@ void main() {
 | 
				
			|||||||
      final newTitle = tester.editor.findDocumentTitle(_testDocumentName);
 | 
					      final newTitle = tester.editor.findDocumentTitle(_testDocumentName);
 | 
				
			||||||
      expect(newTitle, findsOneWidget);
 | 
					      expect(newTitle, findsOneWidget);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWidgets('press arrow down key in title, check if the cursor flashes',
 | 
				
			||||||
 | 
					        (tester) async {
 | 
				
			||||||
 | 
					      await tester.initializeAppFlowy();
 | 
				
			||||||
 | 
					      await tester.tapAnonymousSignInButton();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await tester.createNewPageWithNameUnderParent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      final title = tester.editor.findDocumentTitle('');
 | 
				
			||||||
 | 
					      await tester.enterText(title, _testDocumentName);
 | 
				
			||||||
 | 
					      await tester.pumpAndSettle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
 | 
				
			||||||
 | 
					      const inputText = 'Hello World';
 | 
				
			||||||
 | 
					      await tester.ime.insertText(inputText);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await tester.tapButton(
 | 
				
			||||||
 | 
					        tester.editor.findDocumentTitle(_testDocumentName),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      await tester.simulateKeyEvent(LogicalKeyboardKey.arrowDown);
 | 
				
			||||||
 | 
					      final editorState = tester.editor.getCurrentEditorState();
 | 
				
			||||||
 | 
					      expect(
 | 
				
			||||||
 | 
					        editorState.selection,
 | 
				
			||||||
 | 
					        Selection.collapsed(
 | 
				
			||||||
 | 
					          Position(path: [0], offset: inputText.length),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
 | 
					import 'package:appflowy/generated/locale_keys.g.dart';
 | 
				
			||||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
 | 
					import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
 | 
				
			||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart';
 | 
					import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart';
 | 
				
			||||||
 | 
					import 'package:appflowy/shared/text_field/text_filed_with_metric_lines.dart';
 | 
				
			||||||
import 'package:appflowy/workspace/application/appearance_defaults.dart';
 | 
					import 'package:appflowy/workspace/application/appearance_defaults.dart';
 | 
				
			||||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
 | 
					import 'package:appflowy/workspace/application/view/view_bloc.dart';
 | 
				
			||||||
import 'package:appflowy_backend/log.dart';
 | 
					import 'package:appflowy_backend/log.dart';
 | 
				
			||||||
@ -52,6 +53,7 @@ class _InnerCoverTitleState extends State<_InnerCoverTitle> {
 | 
				
			|||||||
  late final editorContext = context.read<SharedEditorContext>();
 | 
					  late final editorContext = context.read<SharedEditorContext>();
 | 
				
			||||||
  late final editorState = context.read<EditorState>();
 | 
					  late final editorState = context.read<EditorState>();
 | 
				
			||||||
  bool isTitleFocused = false;
 | 
					  bool isTitleFocused = false;
 | 
				
			||||||
 | 
					  int lineCount = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void initState() {
 | 
					  void initState() {
 | 
				
			||||||
@ -111,11 +113,11 @@ class _InnerCoverTitleState extends State<_InnerCoverTitle> {
 | 
				
			|||||||
                    DefaultAppearanceSettings.getDefaultSelectionColor(context),
 | 
					                    DefaultAppearanceSettings.getDefaultSelectionColor(context),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            child: TextField(
 | 
					            child: TextFieldWithMetricLines(
 | 
				
			||||||
              controller: titleTextController,
 | 
					              controller: titleTextController,
 | 
				
			||||||
              focusNode: titleFocusNode,
 | 
					              focusNode: titleFocusNode,
 | 
				
			||||||
              maxLines: null,
 | 
					 | 
				
			||||||
              style: fontStyle,
 | 
					              style: fontStyle,
 | 
				
			||||||
 | 
					              onLineCountChange: (count) => lineCount = count,
 | 
				
			||||||
              decoration: InputDecoration(
 | 
					              decoration: InputDecoration(
 | 
				
			||||||
                border: InputBorder.none,
 | 
					                border: InputBorder.none,
 | 
				
			||||||
                hintText: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
 | 
					                hintText: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
 | 
				
			||||||
@ -175,6 +177,10 @@ class _InnerCoverTitleState extends State<_InnerCoverTitle> {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  KeyEventResult _onKeyEvent(FocusNode focusNode, KeyEvent event) {
 | 
					  KeyEventResult _onKeyEvent(FocusNode focusNode, KeyEvent event) {
 | 
				
			||||||
 | 
					    if (event is KeyUpEvent) {
 | 
				
			||||||
 | 
					      return KeyEventResult.ignored;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (event.logicalKey == LogicalKeyboardKey.enter) {
 | 
					    if (event.logicalKey == LogicalKeyboardKey.enter) {
 | 
				
			||||||
      // if enter is pressed, jump the first line of editor.
 | 
					      // if enter is pressed, jump the first line of editor.
 | 
				
			||||||
      _createNewLine();
 | 
					      _createNewLine();
 | 
				
			||||||
@ -218,7 +224,8 @@ class _InnerCoverTitleState extends State<_InnerCoverTitle> {
 | 
				
			|||||||
    final text = titleTextController.text;
 | 
					    final text = titleTextController.text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // if the cursor is not at the end of the text, ignore the event
 | 
					    // if the cursor is not at the end of the text, ignore the event
 | 
				
			||||||
    if (!selection.isCollapsed || text.length != selection.extentOffset) {
 | 
					    if (lineCount != 1 &&
 | 
				
			||||||
 | 
					        (!selection.isCollapsed || text.length != selection.extentOffset)) {
 | 
				
			||||||
      return KeyEventResult.ignored;
 | 
					      return KeyEventResult.ignored;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TextFieldWithMetricLines extends StatefulWidget {
 | 
				
			||||||
 | 
					  const TextFieldWithMetricLines({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    this.controller,
 | 
				
			||||||
 | 
					    this.focusNode,
 | 
				
			||||||
 | 
					    this.maxLines,
 | 
				
			||||||
 | 
					    this.style,
 | 
				
			||||||
 | 
					    this.decoration,
 | 
				
			||||||
 | 
					    this.onLineCountChange,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final TextEditingController? controller;
 | 
				
			||||||
 | 
					  final FocusNode? focusNode;
 | 
				
			||||||
 | 
					  final int? maxLines;
 | 
				
			||||||
 | 
					  final TextStyle? style;
 | 
				
			||||||
 | 
					  final InputDecoration? decoration;
 | 
				
			||||||
 | 
					  final void Function(int count)? onLineCountChange;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  State<TextFieldWithMetricLines> createState() =>
 | 
				
			||||||
 | 
					      _TextFieldWithMetricLinesState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _TextFieldWithMetricLinesState extends State<TextFieldWithMetricLines> {
 | 
				
			||||||
 | 
					  final key = GlobalKey();
 | 
				
			||||||
 | 
					  late final controller = widget.controller ?? TextEditingController();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void initState() {
 | 
				
			||||||
 | 
					    super.initState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
 | 
				
			||||||
 | 
					      updateDisplayedLineCount(context);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void dispose() {
 | 
				
			||||||
 | 
					    if (widget.controller == null) {
 | 
				
			||||||
 | 
					      // dispose the controller if it was created by this widget
 | 
				
			||||||
 | 
					      controller.dispose();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    super.dispose();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return TextField(
 | 
				
			||||||
 | 
					      key: key,
 | 
				
			||||||
 | 
					      controller: widget.controller,
 | 
				
			||||||
 | 
					      focusNode: widget.focusNode,
 | 
				
			||||||
 | 
					      maxLines: widget.maxLines,
 | 
				
			||||||
 | 
					      style: widget.style,
 | 
				
			||||||
 | 
					      decoration: widget.decoration,
 | 
				
			||||||
 | 
					      onChanged: (_) => updateDisplayedLineCount(context),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // calculate the number of lines that would be displayed in the text field
 | 
				
			||||||
 | 
					  void updateDisplayedLineCount(BuildContext context) {
 | 
				
			||||||
 | 
					    if (widget.onLineCountChange == null) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final renderObject = key.currentContext?.findRenderObject();
 | 
				
			||||||
 | 
					    if (renderObject == null || renderObject is! RenderBox) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final size = renderObject.size;
 | 
				
			||||||
 | 
					    final text = controller.buildTextSpan(
 | 
				
			||||||
 | 
					      context: context,
 | 
				
			||||||
 | 
					      style: widget.style,
 | 
				
			||||||
 | 
					      withComposing: false,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    final textPainter = TextPainter(
 | 
				
			||||||
 | 
					      text: text,
 | 
				
			||||||
 | 
					      textDirection: Directionality.of(context),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    textPainter.layout(minWidth: size.width, maxWidth: size.width);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final lines = textPainter.computeLineMetrics().length;
 | 
				
			||||||
 | 
					    widget.onLineCountChange?.call(lines);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user