mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-11-10 15:38:08 +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